[asterisk-commits] russell: branch russell/cdr-q r248229 - in /team/russell/cdr-q: configs/ incl...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Mon Feb 22 01:02:08 CST 2010


Author: russell
Date: Mon Feb 22 01:02:04 2010
New Revision: 248229

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=248229
Log:
Save off some code.

Modified:
    team/russell/cdr-q/configs/cdr.conf.sample
    team/russell/cdr-q/include/asterisk/cdr.h
    team/russell/cdr-q/main/asterisk.c
    team/russell/cdr-q/main/cdr.c
    team/russell/cdr-q/main/features.c
    team/russell/cdr-q/main/manager.c

Modified: team/russell/cdr-q/configs/cdr.conf.sample
URL: http://svnview.digium.com/svn/asterisk/team/russell/cdr-q/configs/cdr.conf.sample?view=diff&rev=248229&r1=248228&r2=248229
==============================================================================
--- team/russell/cdr-q/configs/cdr.conf.sample (original)
+++ team/russell/cdr-q/configs/cdr.conf.sample Mon Feb 22 01:02:04 2010
@@ -22,42 +22,6 @@
 ; channel on the other, and then one CDR for each channel attempted. This may seem
 ; redundant, but cannot be helped.
 ;unanswered = no
-
-; Define the CDR batch mode, where instead of posting the CDR at the end of
-; every call, the data will be stored in a buffer to help alleviate load on the
-; asterisk server.  Default is "no".
-;
-; WARNING WARNING WARNING
-; Use of batch mode may result in data loss after unsafe asterisk termination
-; ie. software crash, power failure, kill -9, etc.
-; WARNING WARNING WARNING
-;
-;batch=no
-
-; Define the maximum number of CDRs to accumulate in the buffer before posting
-; them to the backend engines.  'batch' must be set to 'yes'.  Default is 100.
-;size=100
-
-; Define the maximum time to accumulate CDRs in the buffer before posting them
-; to the backend engines.  If this time limit is reached, then it will post the
-; records, regardless of the value defined for 'size'.  'batch' must be set to
-; 'yes'.  Note that time is in seconds.  Default is 300 (5 minutes).
-;time=300
-
-; The CDR engine uses the internal asterisk scheduler to determine when to post
-; records.  Posting can either occur inside the scheduler thread, or a new
-; thread can be spawned for the submission of every batch.  For small batches,
-; it might be acceptable to just use the scheduler thread, so set this to "yes".
-; For large batches, say anything over size=10, a new thread is recommended, so
-; set this to "no".  Default is "no".
-;scheduleronly=no
-
-; When shutting down asterisk, you can block until the CDRs are submitted.  If
-; you don't, then data will likely be lost.  You can always check the size of
-; the CDR batch buffer with the CLI "cdr status" command.  To enable blocking on
-; submission of CDR data during asterisk shutdown, set this to "yes".  Default
-; is "yes".
-;safeshutdown=yes
 
 ; Normally, CDR's are not closed out until after all extensions are finished
 ; executing.  By enabling this option, the CDR will be ended before executing

Modified: team/russell/cdr-q/include/asterisk/cdr.h
URL: http://svnview.digium.com/svn/asterisk/team/russell/cdr-q/include/asterisk/cdr.h?view=diff&rev=248229&r1=248228&r2=248229
==============================================================================
--- team/russell/cdr-q/include/asterisk/cdr.h (original)
+++ team/russell/cdr-q/include/asterisk/cdr.h Mon Feb 22 01:02:04 2010
@@ -16,8 +16,9 @@
  * at the top of the source tree.
  */
 
-/*! \file
- * \brief Call Detail Record API 
+/*!
+ * \file
+ * \brief Call Detail Record API
  */
 
 #ifndef _ASTERISK_CDR_H
@@ -70,29 +71,29 @@
 	/*! Caller*ID with text */
 	char clid[AST_MAX_EXTENSION];
 	/*! Caller*ID number */
-	char src[AST_MAX_EXTENSION];		
+	char src[AST_MAX_EXTENSION];
 	/*! Destination extension */
-	char dst[AST_MAX_EXTENSION];		
+	char dst[AST_MAX_EXTENSION];
 	/*! Destination context */
-	char dcontext[AST_MAX_EXTENSION];	
-	
+	char dcontext[AST_MAX_EXTENSION];
+
 	char channel[AST_MAX_EXTENSION];
 	/*! Destination channel if appropriate */
-	char dstchannel[AST_MAX_EXTENSION];	
+	char dstchannel[AST_MAX_EXTENSION];
 	/*! Last application if appropriate */
-	char lastapp[AST_MAX_EXTENSION];	
+	char lastapp[AST_MAX_EXTENSION];
 	/*! Last application data */
-	char lastdata[AST_MAX_EXTENSION];	
-	
+	char lastdata[AST_MAX_EXTENSION];
+
 	struct timeval start;
-	
+
 	struct timeval answer;
-	
+
 	struct timeval end;
 	/*! Total time in system, in seconds */
-	long int duration;				
+	long int duration;
 	/*! Total time call is up, in seconds */
-	long int billsec;				
+	long int billsec;
 	/*! What happened to the call */
 	long int disposition;
 	/*! What flags to use */
@@ -128,38 +129,47 @@
 
 /*!
  * \brief CDR backend callback
+ *
  * \warning CDR backends should NOT attempt to access the channel associated
  * with a CDR record.  This channel is not guaranteed to exist when the CDR
  * backend is invoked.
  */
-typedef int (*ast_cdrbe)(struct ast_cdr *cdr);
-
-/*! \brief Return TRUE if CDR subsystem is enabled */
-int check_cdr_enabled(void);
-
-/*! 
- * \brief Allocate a CDR record 
+typedef int (*ast_cdr_post_cb)(struct ast_cdr *cdr);
+
+/*!
+ * \brief Return TRUE if CDR subsystem is enabled
+ */
+int ast_cdr_enabled(void);
+
+/*!
+ * \brief Allocate a CDR record
+ *
  * \retval a malloc'd ast_cdr structure
  * \retval NULL on error (malloc failure)
  */
 struct ast_cdr *ast_cdr_alloc(void);
 
-/*! 
+/*!
  * \brief Duplicate a record and increment the sequence number.
+ *
  * \param cdr the record to duplicate
- * \retval a malloc'd ast_cdr structure, 
+ *
+ * \retval a malloc'd ast_cdr structure,
  * \retval NULL on error (malloc failure)
+ *
  * \see ast_cdr_dup()
  * \see ast_cdr_dup_unique_swap()
  */
 struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr);
 
-/*! 
- * \brief Duplicate a record and increment the sequence number of the old
- * record.
+/*!
+ * \brief Duplicate a record and increment the sequence number of the old record.
+ *
  * \param cdr the record to duplicate
- * \retval a malloc'd ast_cdr structure, 
+ *
+ * \retval a malloc'd ast_cdr structure,
  * \retval NULL on error (malloc failure)
+ *
  * \note This version increments the original CDR's sequence number rather than
  * the duplicate's sequence number. The effect is as if the original CDR's
  * sequence number was swapped with the duplicate's sequence number.
@@ -169,166 +179,191 @@
  */
 struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr);
 
-/*! 
- * \brief Duplicate a record 
+/*!
+ * \brief Duplicate a record
+ *
  * \param cdr the record to duplicate
- * \retval a malloc'd ast_cdr structure, 
+ *
+ * \retval a malloc'd ast_cdr structure,
  * \retval NULL on error (malloc failure)
+ *
  * \see ast_cdr_dup_unique()
  * \see ast_cdr_dup_unique_swap()
  */
 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr);
 
-/*! 
- * \brief Free a CDR record 
+/*!
+ * \brief Discard and free a CDR record
+ *
  * \param cdr ast_cdr structure to free
- * Returns nothing
- */
-void ast_cdr_free(struct ast_cdr *cdr);
-
-/*! 
- * \brief Discard and free a CDR record 
- * \param cdr ast_cdr structure to free
- * Returns nothing  -- same as free, but no checks or complaints
  */
 void ast_cdr_discard(struct ast_cdr *cdr);
 
-/*! 
+/*!
  * \brief Initialize based on a channel
+ *
  * \param cdr Call Detail Record to use for channel
  * \param chan Channel to bind CDR with
+ *
  * Initializes a CDR and associates it with a particular channel
+ *
  * \return 0 by default
  */
 int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *chan);
 
-/*! 
- * \brief Initialize based on a channel 
+/*!
+ * \brief Initialize based on a channel
+ *
  * \param cdr Call Detail Record to use for channel
  * \param chan Channel to bind CDR with
+ *
  * Initializes a CDR and associates it with a particular channel
+ *
  * \return 0 by default
  */
 int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *chan);
 
-/*! 
- * \brief Register a CDR handling engine 
+/*!
+ * \brief Register a CDR handling engine
+ *
  * \param name name associated with the particular CDR handler
  * \param desc description of the CDR handler
  * \param be function pointer to a CDR handler
+ *
  * Used to register a Call Detail Record handler.
+ *
  * \retval 0 on success.
  * \retval -1 on error
  */
-int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be);
-
-/*! 
- * \brief Unregister a CDR handling engine 
+int ast_cdr_register(const char *name, const char *desc, ast_cdr_post_cb post_cb);
+
+/*!
+ * \brief Unregister a CDR handling engine
+ *
  * \param name name of CDR handler to unregister
+ *
  * Unregisters a CDR by it's name
  */
 void ast_cdr_unregister(const char *name);
 
-/*! 
- * \brief Start a call 
- * \param cdr the cdr you wish to associate with the call
+/*!
+ * \brief Start a call
+ *
+ * \param cdr the cdr you wish to associate with the call
+ *
  * Starts all CDR stuff necessary for monitoring a call
  * Returns nothing
  */
 void ast_cdr_start(struct ast_cdr *cdr);
 
-/*! \brief Answer a call 
- * \param cdr the cdr you wish to associate with the call
+/*!
+ * \brief Answer a call
+ *
+ * \param cdr the cdr you wish to associate with the call
+ *
  * Starts all CDR stuff necessary for doing CDR when answering a call
+ *
  * \note NULL argument is just fine.
  */
 void ast_cdr_answer(struct ast_cdr *cdr);
 
-/*! 
- * \brief A call wasn't answered 
- * \param cdr the cdr you wish to associate with the call
+/*!
+ * \brief A call wasn't answered
+ *
+ * \param cdr the cdr you wish to associate with the call
+ *
  * Marks the channel disposition as "NO ANSWER"
  * Will skip CDR's in chain with ANS_LOCK bit set. (see
  * forkCDR() application.
  */
 extern void ast_cdr_noanswer(struct ast_cdr *cdr);
 
-/*! 
- * \brief Busy a call 
- * \param cdr the cdr you wish to associate with the call
+/*!
+ * \brief Busy a call
+ *
+ * \param cdr the cdr you wish to associate with the call
+ *
  * Marks the channel disposition as "BUSY"
  * Will skip CDR's in chain with ANS_LOCK bit set. (see
  * forkCDR() application.
- * Returns nothing
  */
 void ast_cdr_busy(struct ast_cdr *cdr);
 
-/*! 
- * \brief Fail a call 
- * \param cdr the cdr you wish to associate with the call
+/*!
+ * \brief Fail a call
+ *
+ * \param cdr the cdr you wish to associate with the call
+ *
  * Marks the channel disposition as "FAILED"
  * Will skip CDR's in chain with ANS_LOCK bit set. (see
  * forkCDR() application.
- * Returns nothing
  */
 void ast_cdr_failed(struct ast_cdr *cdr);
 
-/*! 
+/*!
  * \brief Save the result of the call based on the AST_CAUSE_*
+ *
  * \param cdr the cdr you wish to associate with the call
  * \param cause the AST_CAUSE_*
- * Returns nothing
  */
 int ast_cdr_disposition(struct ast_cdr *cdr, int cause);
-	
-/*! 
+
+/*!
  * \brief End a call
+ *
  * \param cdr the cdr you have associated the call with
+ *
  * Registers the end of call time in the cdr structure.
  * Returns nothing
  */
 void ast_cdr_end(struct ast_cdr *cdr);
 
-/*! 
+/*!
  * \brief Detaches the detail record for posting (and freeing) either now or at a
  * later time in bulk with other records during batch mode operation.
+ *
  * \param cdr Which CDR to detach from the channel thread
+ *
  * Prevents the channel thread from blocking on the CDR handling
- * Returns nothing
  */
 void ast_cdr_detach(struct ast_cdr *cdr);
 
-/*! 
- * \brief Spawns (possibly) a new thread to submit a batch of CDRs to the backend engines 
+/*!
+ * \brief Spawns (possibly) a new thread to submit a batch of CDRs to the backend engines
+ *
  * \param shutdown Whether or not we are shutting down
+ *
  * Blocks the asterisk shutdown procedures until the CDR data is submitted.
- * Returns nothing
  */
 void ast_cdr_submit_batch(int shutdown);
 
-/*! 
- * \brief Set the destination channel, if there was one 
+/*!
+ * \brief Set the destination channel, if there was one
+ *
  * \param cdr Which cdr it's applied to
  * \param chan Channel to which dest will be
+ *
  * Sets the destination channel the CDR is applied to
- * Returns nothing
  */
 void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chan);
 
-/*! 
- * \brief Set the last executed application 
+/*!
+ * \brief Set the last executed application
+ *
  * \param cdr which cdr to act upon
  * \param app the name of the app you wish to change it to
  * \param data the data you want in the data field of app you set it to
+ *
  * Changes the value of the last executed app
- * Returns nothing
  */
 void ast_cdr_setapp(struct ast_cdr *cdr, const char *app, const char *data);
 
 /*!
  * \brief Set the answer time for a call
+ *
  * \param cdr the cdr you wish to associate with the call
  * \param t the answer time
+ *
  * Starts all CDR stuff necessary for doing CDR when answering a call
  * NULL argument is just fine.
  */
@@ -336,92 +371,127 @@
 
 /*!
  * \brief Set the disposition for a call
+ *
  * \param cdr the cdr you wish to associate with the call
  * \param disposition the new disposition
+ *
  * Set the disposition on a call.
  * NULL argument is just fine.
  */
 void ast_cdr_setdisposition(struct ast_cdr *cdr, long int disposition);
 
-/*! 
- * \brief Convert a string to a detail record AMA flag 
+/*!
+ * \brief Convert a string to a detail record AMA flag
+ *
  * \param flag string form of flag
+ *
  * Converts the string form of the flag to the binary form.
+ *
  * \return the binary form of the flag
  */
 int ast_cdr_amaflags2int(const char *flag);
 
-/*! 
- * \brief Disposition to a string 
+/*!
+ * \brief Disposition to a string
+ *
  * \param disposition input binary form
+ *
  * Converts the binary form of a disposition to string form.
+ *
  * \return a pointer to the string form
  */
 char *ast_cdr_disp2str(int disposition);
 
-/*! 
- * \brief Reset the detail record, optionally posting it first 
+/*!
+ * \brief Reset the detail record, optionally posting it first
+ *
  * \param cdr which cdr to act upon
  * \param flags |AST_CDR_FLAG_POSTED whether or not to post the cdr first before resetting it
  *              |AST_CDR_FLAG_LOCKED whether or not to reset locked CDR's
  */
 void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *flags);
 
-/*! Reset the detail record times, flags */
-/*!
+/*!
+ * \brief Reset the detail record times, flags
+ *
  * \param cdr which cdr to act upon
  * \param flags |AST_CDR_FLAG_POSTED whether or not to post the cdr first before resetting it
  *              |AST_CDR_FLAG_LOCKED whether or not to reset locked CDR's
  */
 void ast_cdr_specialized_reset(struct ast_cdr *cdr, struct ast_flags *flags);
 
-/*! Flags to a string */
-/*!
+/*!
+ * \brief Convert amaflags to a string
+ *
  * \param flags binary flag
+ *
  * Converts binary flags to string flags
- * Returns string with flag name
+ *
+ * \return string with flag name
  */
 char *ast_cdr_flags2str(int flags);
 
-/*! 
+/*!
  * \brief Move the non-null data from the "from" cdr to the "to" cdr
+ *
  * \param to the cdr to get the goodies
  * \param from the cdr to give the goodies
  */
 void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from);
 
-/*! \brief Set account code, will generate AMI event */
+/*!
+ * \brief Set account code, will generate AMI event
+ */
 int ast_cdr_setaccount(struct ast_channel *chan, const char *account);
 
-/*! \brief Set the peer account */
+/*!
+ * \brief Set the peer account
+ */
 int ast_cdr_setpeeraccount(struct ast_channel *chan, const char *account);
 
-/*! \brief Set AMA flags for channel */
+/*!
+ * \brief Set AMA flags for channel
+ */
 int ast_cdr_setamaflags(struct ast_channel *chan, const char *amaflags);
 
-/*! \brief Set CDR user field for channel (stored in CDR) */
+/*!
+ * \brief Set CDR user field for channel (stored in CDR)
+ */
 int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield);
-/*! \brief Append to CDR user field for channel (stored in CDR) */
+
+/*!
+ * \brief Append to CDR user field for channel (stored in CDR)
+ */
 int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield);
 
 
-/*! Update CDR on a channel */
+/*!
+ * \brief Update CDR on a channel
+ */
 int ast_cdr_update(struct ast_channel *chan);
 
-
+/*!
+ * \brief Default AMA flags for CDRs
+ */
 extern int ast_default_amaflags;
 
 extern char ast_default_accountcode[AST_MAX_ACCOUNT_CODE];
 
 struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr);
 
-/*! \brief Reload the configuration file cdr.conf and start/stop CDR scheduling thread */
+/*!
+ * \brief Reload the configuration file cdr.conf and start/stop CDR scheduling thread
+ */
 int ast_cdr_engine_reload(void);
 
-/*! \brief Load the configuration file cdr.conf and possibly start the CDR scheduling thread */
+/*!
+ * \brief Load the configuration file cdr.conf and possibly start the CDR scheduling thread
+ */
 int ast_cdr_engine_init(void);
 
-/*! Submit any remaining CDRs and prepare for shutdown */
+/*!
+ * \brief Submit any remaining CDRs and prepare for shutdown
+ */
 void ast_cdr_engine_term(void);
 
 #endif /* _ASTERISK_CDR_H */

Modified: team/russell/cdr-q/main/asterisk.c
URL: http://svnview.digium.com/svn/asterisk/team/russell/cdr-q/main/asterisk.c?view=diff&rev=248229&r1=248228&r2=248229
==============================================================================
--- team/russell/cdr-q/main/asterisk.c (original)
+++ team/russell/cdr-q/main/asterisk.c Mon Feb 22 01:02:04 2010
@@ -470,7 +470,7 @@
 	ast_cli(a->fd, "  -------------\n");
 	ast_cli(a->fd, "  Manager (AMI):               %s\n", check_manager_enabled() ? "Enabled" : "Disabled");
 	ast_cli(a->fd, "  Web Manager (AMI/HTTP):      %s\n", check_webmanager_enabled() ? "Enabled" : "Disabled");
-	ast_cli(a->fd, "  Call data records:           %s\n", check_cdr_enabled() ? "Enabled" : "Disabled");
+	ast_cli(a->fd, "  Call data records:           %s\n", ast_cdr_enabled() ? "Enabled" : "Disabled");
 	ast_cli(a->fd, "  Realtime Architecture (ARA): %s\n", ast_realtime_enabled() ? "Enabled" : "Disabled");
 
 	/*! \todo we could check musiconhold, voicemail, smdi, adsi, queues  */

Modified: team/russell/cdr-q/main/cdr.c
URL: http://svnview.digium.com/svn/asterisk/team/russell/cdr-q/main/cdr.c?view=diff&rev=248229&r1=248228&r2=248229
==============================================================================
--- team/russell/cdr-q/main/cdr.c (original)
+++ team/russell/cdr-q/main/cdr.c Mon Feb 22 01:02:04 2010
@@ -1,9 +1,10 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 1999 - 2006, Digium, Inc.
+ * Copyright (C) 1999 - 2010, Digium, Inc.
  *
  * Mark Spencer <markster at digium.com>
+ * Russell Bryant <russell at digium.com>
  *
  * See http://www.asterisk.org for more information about
  * the Asterisk project. Please do not directly contact
@@ -16,18 +17,12 @@
  * at the top of the source tree.
  */
 
-/*! \file
- *
- * \brief Call Detail Record API 
+/*!
+ * \file
+ * \brief Call Detail Record API
  *
  * \author Mark Spencer <markster at digium.com>
- * 
- * \note Includes code and algorithms from the Zapata library.
- *
- * \note We do a lot of checking here in the CDR code to try to be sure we don't ever let a CDR slip
- * through our fingers somehow.  If someone allocates a CDR, it must be completely handled normally
- * or a WARNING shall be logged, so that we can best keep track of any escape condition where the CDR
- * isn't properly generated and posted.
+ * \author Russell Bryant <russell at digium.com>
  */
 
 
@@ -35,176 +30,269 @@
 
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
-#include <signal.h>
-
-#include "asterisk/lock.h"
 #include "asterisk/channel.h"
 #include "asterisk/cdr.h"
-#include "asterisk/callerid.h"
 #include "asterisk/manager.h"
 #include "asterisk/causes.h"
-#include "asterisk/linkedlists.h"
 #include "asterisk/utils.h"
-#include "asterisk/sched.h"
 #include "asterisk/config.h"
 #include "asterisk/cli.h"
 #include "asterisk/stringfields.h"
-
-/*! Default AMA flag for billing records (CDR's) */
-int ast_default_amaflags = AST_CDR_DOCUMENTATION;
+#include "asterisk/linkedlists.h"
+#include "asterisk/taskprocessor.h"
+
+/*!
+ * \internal
+ * \brief Hack to be able to link a CDR struct into a list head
+ */
+struct cdr_wrap {
+	struct ast_cdr *cdr;
+	AST_LIST_ENTRY(cdr_wrap) entry;
+};
+
+/*!
+ * \brief A CDR logging backend
+ */
+struct cdr_backend {
+	AST_DECLARE_STRING_FIELDS(
+		/*!
+		 * \brief Unique name for this backend
+		 */
+		AST_STRING_FIELD(name);
+		/*!
+		 * \brief Description of this backend
+		 */
+		AST_STRING_FIELD(desc);
+	);
+	/*!
+	 * \brief Callback into the backend for posting CDR
+	 */
+	ast_cdr_post_cb post_cb;
+	/*!
+	 * \brief A taskprocessor for asynchronously posting CDRs to this backend
+	 */
+	struct ast_taskprocessor *tps;
+	/*!
+	 * \brief A queue of CDRs not yet posted to this backend
+	 */
+	AST_LIST_HEAD_NOLOCK(, cdr_wrap) cdr_q;
+	/*!
+	 * \brief Current length of the CDR queue
+	 */
+	unsigned int cdr_q_len;
+	/*!
+	 * \brief A cache of cdr_wrap objects for use with this backend
+	 */
+	struct ao2_container *cdr_wrap_cache;
+	/*!
+	 * \brief The last timestamp of a post failure log entry
+	 */
+	struct timeval last_error;
+};
+
+/*!
+ * \brief Cache size limit for cdr wrappers
+ */
+static const unsigned int WRAP_CACHE_SIZE_MAX = 10;
+
+/*!
+ * \brief Limit LOG_ERROR messages to 1 per 5 seconds.
+ */
+static const unsigned int ERROR_RATE_LIMIT = 5000;
+
+static struct ao2_container *cdr_backends;
+
+/*!
+ * \brief CDR subsystem configuration parameters
+ */
+static struct cdr_cfg {
+	/*!
+	 * \brief Is CDR logging enabled?
+	 */
+	unsigned int enabled:1;
+	/*!
+	 * \brief Log unanswered outbound call legs?
+	 */
+	unsigned int unanswered:1;
+} cdr_cfg;
+
+static const struct cdr_cfg cdr_cfg_default = {
+	.enabled = 1,
+	.unanswered = 0,
+};
+
+static int cdr_sequence;
+
+int ast_default_amaflags;
+static const int DEFAULT_AMAFLAGS = AST_CDR_DOCUMENTATION;
+
 char ast_default_accountcode[AST_MAX_ACCOUNT_CODE];
-
-struct ast_cdr_beitem {
-	char name[20];
-	char desc[80];
-	ast_cdrbe be;
-	AST_RWLIST_ENTRY(ast_cdr_beitem) list;
-};
-
-static AST_RWLIST_HEAD_STATIC(be_list, ast_cdr_beitem);
-
-struct ast_cdr_batch_item {
-	struct ast_cdr *cdr;
-	struct ast_cdr_batch_item *next;
-};
-
-static struct ast_cdr_batch {
-	int size;
-	struct ast_cdr_batch_item *head;
-	struct ast_cdr_batch_item *tail;
-} *batch = NULL;
-
-
-static int cdr_sequence =  0;
-
-static int cdr_seq_inc(struct ast_cdr *cdr);
-
-static struct sched_context *sched;
-static int cdr_sched = -1;
-static pthread_t cdr_thread = AST_PTHREADT_NULL;
-
-#define BATCH_SIZE_DEFAULT 100
-#define BATCH_TIME_DEFAULT 300
-#define BATCH_SCHEDULER_ONLY_DEFAULT 0
-#define BATCH_SAFE_SHUTDOWN_DEFAULT 1
-
-static int enabled;		/*! Is the CDR subsystem enabled ? */
-static int unanswered;
-static int batchmode;
-static int batchsize;
-static int batchtime;
-static int batchscheduleronly;
-static int batchsafeshutdown;
-
-AST_MUTEX_DEFINE_STATIC(cdr_batch_lock);
-
-/* these are used to wake up the CDR thread when there's work to do */
-AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
-static ast_cond_t cdr_pending_cond;
-
-int check_cdr_enabled()
-{
-	return enabled;
-}
-
-/*! Register a CDR driver. Each registered CDR driver generates a CDR 
-	\return 0 on success, -1 on failure 
-*/
-int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
-{
-	struct ast_cdr_beitem *i = NULL;
-
-	if (!name)
+static const char DEFAULT_ACCOUNTCODE[] = "";
+
+int ast_cdr_enabled(void)
+{
+	return cdr_cfg.enabled;
+}
+
+/*!
+ * \internal
+ * \brief Search for a CDR backend by name
+ *
+ * \arg name the name of a backend to search for
+ *
+ * \return an astobj2 reference to a cdr_backend, or NULL if not found
+ */
+static struct cdr_backend *find_cdr_backend(const char *name)
+{
+	struct cdr_backend tmp = {
+		.name = name,
+	};
+
+	return ao2_find(cdr_backends, &tmp, OBJ_POINTER);
+}
+
+static void cdr_backend_destructor(void *obj)
+{
+	struct cdr_backend *backend = obj;
+
+	if (backend->tps) {
+		backend->tps = ast_taskprocessor_unreference(backend->tps);
+	}
+
+	if (backend->cdr_wrap_cache) {
+		ao2_t_ref(backend->cdr_wrap_cache, -1, "Bye-Bye cdr_wrap cache");
+		backend->cdr_wrap_cache = NULL;
+	}
+
+	ast_string_field_free_memory(backend);
+}
+
+int ast_cdr_register(const char *name, const char *desc, ast_cdr_post_cb post_cb)
+{
+	struct cdr_backend *backend;
+	char tps_name[128];
+	int res = 0;
+
+	if (ast_strlen_zero(name)) {
+		ast_log(LOG_WARNING, "Not registering a CDR backend with no name\n");
 		return -1;
-
-	if (!be) {
-		ast_log(LOG_WARNING, "CDR engine '%s' lacks backend\n", name);
+	}
+
+	if (ast_strlen_zero(desc)) {
+		ast_log(LOG_WARNING, "Not registering CDR backend '%s' with no description\n", name);
 		return -1;
 	}
 
-	AST_RWLIST_WRLOCK(&be_list);
-	AST_RWLIST_TRAVERSE(&be_list, i, list) {
-		if (!strcasecmp(name, i->name)) {
-			ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name);
-			AST_RWLIST_UNLOCK(&be_list);
-			return -1;
-		}
-	}
-
-	if (!(i = ast_calloc(1, sizeof(*i)))) 	
+	if (!post_cb) {
+		ast_log(LOG_WARNING, "Not registering CDR backend '%s' with no callback\n", name);
 		return -1;
-
-	i->be = be;
-	ast_copy_string(i->name, name, sizeof(i->name));
-	ast_copy_string(i->desc, desc, sizeof(i->desc));
-
-	AST_RWLIST_INSERT_HEAD(&be_list, i, list);
-	AST_RWLIST_UNLOCK(&be_list);
-
-	return 0;
-}
-
-/*! unregister a CDR driver */
+	}
+
+	if ((backend = find_cdr_backend(name))) {
+		ao2_t_ref(backend, -1, "Found a backend that already existed");
+		backend = NULL;
+		ast_log(LOG_WARNING, "CDR backend named '%s' already exists\n", name);
+		return -1;
+	}
+
+	if (!(backend = ao2_alloc(sizeof(*backend), cdr_backend_destructor))) {
+		return -1;
+	}
+
+	snprintf(tps_name, sizeof(tps_name), "CDR-BACKEND-%s", name);
+
+	if (!(backend->tps = ast_taskprocessor_get(tps_name, TPS_REF_DEFAULT))) {
+		res = -1;
+		goto return_cleanup;
+	}
+
+	if (ast_string_field_init(backend, 128)) {
+		res = -1;
+		goto return_cleanup;
+	}
+
+	if (!(backend->cdr_wrap_cache = ao2_container_alloc(1, NULL, NULL))) {
+		res = -1;
+		goto return_cleanup;
+	}
+
+	ast_string_field_set(backend, name, name);
+	ast_string_field_set(backend, desc, desc);
+	backend->post_cb = post_cb;
+
+	ao2_link(cdr_backends, backend);
+
+return_cleanup:
+	ao2_t_ref(backend, -1, "Done registering a backend");
+
+	return res;
+}
+
 void ast_cdr_unregister(const char *name)
 {
-	struct ast_cdr_beitem *i = NULL;
-
-	AST_RWLIST_WRLOCK(&be_list);
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
-		if (!strcasecmp(name, i->name)) {
-			AST_RWLIST_REMOVE_CURRENT(list);
-			break;
-		}
-	}
-	AST_RWLIST_TRAVERSE_SAFE_END;
-	AST_RWLIST_UNLOCK(&be_list);
-
-	if (i) {
-		ast_verb(2, "Unregistered '%s' CDR backend\n", name);
-		ast_free(i);
-	}
+	struct cdr_backend *backend;
+
+	if (!(backend = find_cdr_backend(name))) {
+		ast_log(LOG_WARNING, "CDR backend '%s' not found to unregister\n", name);
+		return;
+	}
+
+	ao2_unlink(cdr_backends, backend);
+
+	ao2_t_ref(backend, -1, "backend unregister");
+
+	ast_verb(2, "Unregistered '%s' CDR backend\n", name);
 }
 
 int ast_cdr_isset_unanswered(void)
 {
-	return unanswered;
-}
-
-struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr) 
+	return cdr_cfg.unanswered;
+}
+
+static inline void cdr_seq_inc(struct ast_cdr *cdr)
+{
+	cdr->sequence = ast_atomic_fetchadd_int(&cdr_sequence, +1);
+}
+
+struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr)
 {
 	struct ast_cdr *newcdr = ast_cdr_dup(cdr);
-	if (!newcdr)
+
+	if (!newcdr) {
 		return NULL;
+	}
 
 	cdr_seq_inc(newcdr);
+
 	return newcdr;
 }
 
-struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr) 
+struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr)
 {
 	struct ast_cdr *newcdr = ast_cdr_dup(cdr);
-	if (!newcdr)
+
+	if (!newcdr) {
 		return NULL;
+	}
 
 	cdr_seq_inc(cdr);
+
 	return newcdr;
 }
 
-/*! Duplicate a CDR record 
-	\returns Pointer to new CDR record
-*/
-struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr) 
+struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
 {
 	struct ast_cdr *newcdr;
-	
-	if (!cdr) /* don't die if we get a null cdr pointer */
+
+	if (!cdr) {
 		return NULL;
-	newcdr = ast_cdr_alloc();
-	if (!newcdr)
+	}
+
+	if (!(newcdr = ast_cdr_alloc())) {
 		return NULL;
-
-	memcpy(newcdr, cdr, sizeof(*newcdr));
-	/* The varshead is unusable, volatile even, after the memcpy so we take care of that here */
+	}
+
+	*newcdr = *cdr;
 	memset(&newcdr->varshead, 0, sizeof(newcdr->varshead));
 	ast_cdr_copy_vars(newcdr, cdr);
 	newcdr->next = NULL;
@@ -212,17 +300,19 @@
 	return newcdr;
 }
 
-static const char *ast_cdr_getvar_internal(struct ast_cdr *cdr, const char *name, int recur) 
-{
-	if (ast_strlen_zero(name))
+static const char *ast_cdr_getvar_internal(struct ast_cdr *cdr, const char *name, int recur)
+{
+	if (ast_strlen_zero(name)) {
 		return NULL;
+	}
 
 	for (; cdr; cdr = recur ? cdr->next : NULL) {
 		struct ast_var_t *variables;
 		struct varshead *headp = &cdr->varshead;
 		AST_LIST_TRAVERSE(headp, variables, entries) {
-			if (!strcasecmp(name, ast_var_name(variables)))
+			if (!strcasecmp(name, ast_var_name(variables))) {
 				return ast_var_value(variables);
+			}
 		}
 	}
 
@@ -231,20 +321,21 @@
 
 static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufsize)
 {
-	if (fmt == NULL) {	/* raw mode */
+	if (!fmt) { /* raw mode */
 		snprintf(buf, bufsize, "%ld.%06ld", (long)when.tv_sec, (long)when.tv_usec);
-	} else {
-		if (when.tv_sec) {
-			struct ast_tm tm;
-			
-			ast_localtime(&when, &tm, NULL);
-			ast_strftime(buf, bufsize, fmt, &tm);
-		}
-	}
-}
-
-/*! CDR channel variable retrieval */
-void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw) 
+		return;
+	}
+
+	if (when.tv_sec) {
+		struct ast_tm tm;
+
+		ast_localtime(&when, &tm, NULL);
+		ast_strftime(buf, bufsize, fmt, &tm);
+	}
+}
+
+void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret,
+		char *workspace, int workspacelen, int recur, int raw)
 {
 	const char *fmt = "%Y-%m-%d %T";
 	const char *varbuf;
@@ -253,7 +344,7 @@
 		return;
 
 	*ret = NULL;
-	/* special vars (the ones from the struct ast_cdr when requested by name) 
+	/* special vars (the ones from the struct ast_cdr when requested by name)
 	   I'd almost say we should convert all the stringed vals to vars */
 
 	if (!strcasecmp(name, "clid"))
@@ -311,24 +402,27 @@
 	else
 		workspace[0] = '\0';
 
-	if (!ast_strlen_zero(workspace))
+	if (!ast_strlen_zero(workspace)) {
 		*ret = workspace;
-}
-
-/* readonly cdr variables */
-static const char * const cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel",
-						  "lastapp", "lastdata", "start", "answer", "end", "duration",
-						  "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "linkedid",
-						  "userfield", "sequence", NULL };
-/*! Set a CDR channel variable 
-	\note You can't set the CDR variables that belong to the actual CDR record, like "billsec".
-*/
-int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur) 
+	}
+}
+
+/*!
+ * \brief Read-only CDR variables
+ */
+static const char * const cdr_readonly_vars[] = {
+	"clid", "src", "dst", "dcontext", "channel", "dstchannel",
+	"lastapp", "lastdata", "start", "answer", "end", "duration",
+	"billsec", "disposition", "amaflags", "accountcode", "uniqueid",
+	"linkedid", "userfield", "sequence", NULL
+};
+
+int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur)
 {
 	struct ast_var_t *newvariable;
 	struct varshead *headp;
 	int x;
-	
+
 	for (x = 0; cdr_readonly_vars[x]; x++) {
 		if (!strcasecmp(name, cdr_readonly_vars[x])) {
 			ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!.\n", name);
@@ -354,7 +448,7 @@
 			}
 		}
 		AST_LIST_TRAVERSE_SAFE_END;
-		
+
 		if (value) {
 			newvariable = ast_var_assign(name, value);
 			AST_LIST_INSERT_HEAD(headp, newvariable, entries);
@@ -371,8 +465,9 @@
 	const char *var, *val;
 	int x = 0;
 
-	if (!to_cdr || !from_cdr) /* don't die if one of the pointers is null */
+	if (!to_cdr || !from_cdr) {
 		return 0;
+	}
 
 	headpa = &from_cdr->varshead;
 	headpb = &to_cdr->varshead;
@@ -390,7 +485,7 @@
 	return x;
 }
 
-int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur) 
+int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur)
 {
 	struct ast_var_t *variables;
 	const char *var, *val;
@@ -401,33 +496,38 @@
 	ast_str_reset(*buf);
 
 	for (; cdr; cdr = recur ? cdr->next : NULL) {
-		if (++x > 1)
+		if (++x > 1) {
 			ast_str_append(buf, 0, "\n");
+		}
 
 		AST_LIST_TRAVERSE(&cdr->varshead, variables, entries) {
 			if (variables &&
 			    (var = ast_var_name(variables)) && (val = ast_var_value(variables)) &&
 			    !ast_strlen_zero(var) && !ast_strlen_zero(val)) {
 				if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, val, sep) < 0) {
- 					ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
- 					break;
-				} else
+					ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
+					break;
+				} else {
 					total++;
-			} else 
+				}
+			} else {
 				break;
+			}
 		}
 
 		for (i = 0; cdr_readonly_vars[i]; i++) {
 			workspace[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
 			ast_cdr_getvar(cdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0);
-			if (!tmp)
+			if (!tmp) {
 				continue;
-			
+			}
+
 			if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, tmp, sep) < 0) {
 				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
 				break;
-			} else
+			} else {
 				total++;
+			}
 		}
 	}
 
@@ -437,56 +537,50 @@
 
 void ast_cdr_free_vars(struct ast_cdr *cdr, int recur)
 {
-
-	/* clear variables */
 	for (; cdr; cdr = recur ? cdr->next : NULL) {
 		struct ast_var_t *vardata;
 		struct varshead *headp = &cdr->varshead;
-		while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries)))
+		while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) {
 			ast_var_delete(vardata);
-	}
-}
-
-/*! \brief  print a warning if cdr already posted */
+		}
+	}
+}
+
+/*!
+ * \internal
+ * \brief print a warning if CDR already posted
+ */
 static void check_post(struct ast_cdr *cdr)
 {
-	if (!cdr)
+	if (!cdr) {
 		return;
-	if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
-		ast_log(LOG_NOTICE, "CDR on channel '%s' already posted\n", S_OR(cdr->channel, "<unknown>"));
-}
-
-void ast_cdr_free(struct ast_cdr *cdr)
-{
-
-	while (cdr) {
-		struct ast_cdr *next = cdr->next;
-
-		ast_cdr_free_vars(cdr, 0);
-		ast_free(cdr);
-		cdr = next;
-	}
-}
-
-/*! \brief the same as a cdr_free call, only with no checks; just get rid of it */
+	}
+
+	if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED)) {
+		ast_log(LOG_NOTICE, "CDR on channel '%s' already posted\n",
+				S_OR(cdr->channel, "<unknown>"));
+	}
+}
+
 void ast_cdr_discard(struct ast_cdr *cdr)
 {
-	while (cdr) {
-		struct ast_cdr *next = cdr->next;
-
-		ast_cdr_free_vars(cdr, 0);
-		ast_free(cdr);
-		cdr = next;
-	}
+	struct ast_cdr *next = cdr->next;
+
+	for (; cdr; cdr = next, next = cdr ? cdr->next : NULL) {
+		ao2_t_ref(cdr, -1, "discarding cdr");
+	}
+}
+
+static void ast_cdr_destructor(void *obj)
+{
+	struct ast_cdr *cdr = obj;
+
+	ast_cdr_free_vars(cdr, 0);
 }
 
 struct ast_cdr *ast_cdr_alloc(void)
 {
-	struct ast_cdr *x;
-	x = ast_calloc(1, sizeof(*x));
-	if (!x)
-		ast_log(LOG_ERROR,"Allocation Failure for a CDR!\n");
-	return x;
+	return ao2_alloc(sizeof(struct ast_cdr), ast_cdr_destructor);
 }
 
 static void cdr_merge_vars(struct ast_cdr *to, struct ast_cdr *from)
@@ -494,6 +588,7 @@
 	struct ast_var_t *variablesfrom,*variablesto;
 	struct varshead *headpfrom = &to->varshead;
 	struct varshead *headpto = &from->varshead;
+
 	AST_LIST_TRAVERSE_SAFE_BEGIN(headpfrom, variablesfrom, entries) {
 		/* for every var in from, stick it in to */
 		const char *fromvarname, *fromvarval;
@@ -504,9 +599,8 @@
 
 		/* now, quick see if that var is in the 'to' cdr already */
 		AST_LIST_TRAVERSE(headpto, variablesto, entries) {
-
 			/* now, quick see if that var is in the 'to' cdr already */
-			if ( strcasecmp(fromvarname, ast_var_name(variablesto)) == 0 ) {
+			if (!strcasecmp(fromvarname, ast_var_name(variablesto))) {
 				tovarname = ast_var_name(variablesto);
 				tovarval = ast_var_value(variablesto);
 				break;
@@ -515,8 +609,9 @@
 		if (tovarname && strcasecmp(fromvarval,tovarval) != 0) {  /* this message here to see how irritating the userbase finds it */
 			ast_log(LOG_NOTICE, "Merging CDR's: variable %s value %s dropped in favor of value %s\n", tovarname, fromvarval, tovarval);
 			continue;
-		} else if (tovarname && strcasecmp(fromvarval,tovarval) == 0) /* if they are the same, the job is done */
+		} else if (tovarname && !strcasecmp(fromvarval,tovarval)) { /* if they are the same, the job is done */
 			continue;
+		}
 
 		/* rip this var out of the from cdr, and stick it in the to cdr */
 		AST_LIST_MOVE_CURRENT(headpto, entries);
@@ -530,9 +625,10 @@
 	struct ast_cdr *lto = NULL;
 	struct ast_cdr *lfrom = NULL;
 	int discard_from = 0;
-	
-	if (!to || !from)
+
+	if (!to || !from) {
 		return;
+	}
 
 	/* don't merge into locked CDR's -- it's bad business */
 	if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
@@ -541,7 +637,7 @@
 			lto = to;
 			to = to->next;
 		}
-		
+
 		if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
 			ast_log(LOG_WARNING, "Merging into locked CDR... no choice.");
 			to = zcdr; /* safety-- if all there are is locked CDR's, then.... ?? */
@@ -557,9 +653,10 @@
 			lto->next = from;
 			lfrom = from;
 			while (lfrom && lfrom->next) {
-				if (!lfrom->next->next)
+				if (!lfrom->next->next) {
 					llfrom = lfrom;
-				lfrom = lfrom->next; 
+				}
+				lfrom = lfrom->next;
 			}
 			/* rip off the last entry and put a copy of the to at the end */
 			llfrom->next = to;
@@ -567,25 +664,27 @@
 		} else {
 			/* save copy of the current *to cdr */
 			struct ast_cdr tcdr;
-			memcpy(&tcdr, to, sizeof(tcdr));
+			tcdr = *to;
 			/* copy in the locked from cdr */
-			memcpy(to, from, sizeof(*to));
+			*to = *from;
 			lfrom = from;
 			while (lfrom && lfrom->next) {
-				if (!lfrom->next->next)
+				if (!lfrom->next->next) {
 					llfrom = lfrom;
-				lfrom = lfrom->next; 
+				}
+				lfrom = lfrom->next;
 			}
 			from->next = NULL;
 			/* rip off the last entry and put a copy of the to at the end */
-			if (llfrom == from)
+			if (llfrom == from) {
 				to = to->next = ast_cdr_dup(&tcdr);
-			else
+			} else {
 				to = llfrom->next = ast_cdr_dup(&tcdr);
+			}
 			from = lfrom;
 		}
 	}
-	
+
 	if (!ast_tvzero(from->start)) {
 		if (!ast_tvzero(to->start)) {
 			if (ast_tvcmp(to->start, from->start) > 0 ) {
@@ -681,16 +780,21 @@
 	/* flags, varsead, ? */
 	cdr_merge_vars(from, to);
 
-	if (ast_test_flag(from, AST_CDR_FLAG_KEEP_VARS))
+	if (ast_test_flag(from, AST_CDR_FLAG_KEEP_VARS)) {
 		ast_set_flag(to, AST_CDR_FLAG_KEEP_VARS);
-	if (ast_test_flag(from, AST_CDR_FLAG_POSTED))
+	}
+	if (ast_test_flag(from, AST_CDR_FLAG_POSTED)) {
 		ast_set_flag(to, AST_CDR_FLAG_POSTED);
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED))
+	}
+	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED)) {
 		ast_set_flag(to, AST_CDR_FLAG_LOCKED);
-	if (ast_test_flag(from, AST_CDR_FLAG_CHILD))
+	}
+	if (ast_test_flag(from, AST_CDR_FLAG_CHILD)) {
 		ast_set_flag(to, AST_CDR_FLAG_CHILD);
-	if (ast_test_flag(from, AST_CDR_FLAG_POST_DISABLED))
+	}
+	if (ast_test_flag(from, AST_CDR_FLAG_POST_DISABLED)) {
 		ast_set_flag(to, AST_CDR_FLAG_POST_DISABLED);
+	}
 
 	/* last, but not least, we need to merge any forked CDRs to the 'to' cdr */
 	while (from->next) {
@@ -701,13 +805,14 @@
 		/* zcdr is now ripped from the current list; */
 		ast_cdr_append(to, zcdr);
 	}
-	if (discard_from)
+	if (discard_from) {
 		ast_cdr_discard(from);
+	}
 }
 
 void ast_cdr_start(struct ast_cdr *cdr)
 {
-	char *chan; 
+	char *chan;
 
 	for (; cdr; cdr = cdr->next) {

[... 1316 lines stripped ...]



More information about the asterisk-commits mailing list