From 3713fa5c9f9bab64f21a3602c4a201bcb2458084 Mon Sep 17 00:00:00 2001
From: Matthew Jordan <mjordan@digium.com>
Date: Sun, 27 Oct 2013 20:04:17 +0000
Subject: [PATCH] Prevent CDR backends from unregistering while billing data is
 in flight

This patch makes it so that CDR backends cannot be unregistered while active
CDR records exist. This helps to prevent billing data from being lost during
restarts and shutdowns.

Review: https://reviewboard.asterisk.org/r/2880/
........

Merged revisions 402081 from http://svn.asterisk.org/svn/asterisk/branches/12


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@402082 65c4cc65-6c06-0410-ace0-fbb531ad65f3
---
 addons/cdr_mysql.c       | 14 +++++---
 cdr/cdr_adaptive_odbc.c  |  5 ++-
 cdr/cdr_csv.c            |  5 ++-
 cdr/cdr_custom.c         |  4 ++-
 cdr/cdr_manager.c        | 24 +++++++++----
 cdr/cdr_odbc.c           |  8 +++--
 cdr/cdr_pgsql.c          |  5 ++-
 cdr/cdr_radius.c         |  5 ++-
 cdr/cdr_sqlite.c         |  5 ++-
 cdr/cdr_sqlite3_custom.c |  4 ++-
 cdr/cdr_syslog.c         |  4 ++-
 cdr/cdr_tds.c            |  6 ++--
 include/asterisk/cdr.h   | 21 ++++++++++-
 main/cdr.c               | 78 +++++++++++++++++++++++++++++++++-------
 14 files changed, 152 insertions(+), 36 deletions(-)

diff --git a/addons/cdr_mysql.c b/addons/cdr_mysql.c
index 25b55b35f6..b00a3f3451 100644
--- a/addons/cdr_mysql.c
+++ b/addons/cdr_mysql.c
@@ -384,9 +384,11 @@ static int my_unload_module(int reload)
 	}
 
 	dbport = 0;
-	ast_cdr_unregister(name);
-	
-	return 0;
+	if (reload) {
+		return ast_cdr_backend_suspend(name);
+	} else {
+		return ast_cdr_unregister(name);
+	}
 }
 
 static int my_load_config_string(struct ast_config *cfg, const char *category, const char *variable, struct ast_str **field, const char *def)
@@ -660,7 +662,11 @@ static int my_load_module(int reload)
 		return AST_MODULE_LOAD_FAILURE;
 	}
 
-	res = ast_cdr_register(name, desc, mysql_log);
+	if (!reload) {
+		res = ast_cdr_register(name, desc, mysql_log);
+	} else {
+		res = ast_cdr_backend_unsuspend(name);
+	}
 	if (res) {
 		ast_log(LOG_ERROR, "Unable to register MySQL CDR handling\n");
 	} else {
diff --git a/cdr/cdr_adaptive_odbc.c b/cdr/cdr_adaptive_odbc.c
index 0a9cfdbdd9..4078b79ba7 100644
--- a/cdr/cdr_adaptive_odbc.c
+++ b/cdr/cdr_adaptive_odbc.c
@@ -767,7 +767,10 @@ early_release:
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
+
 	if (AST_RWLIST_WRLOCK(&odbc_tables)) {
 		ast_cdr_register(name, ast_module_info->description, odbc_log);
 		ast_log(LOG_ERROR, "Unable to lock column list.  Unload failed.\n");
diff --git a/cdr/cdr_csv.c b/cdr/cdr_csv.c
index a6f8a4dc0a..1cc1747c67 100644
--- a/cdr/cdr_csv.c
+++ b/cdr/cdr_csv.c
@@ -315,7 +315,10 @@ static int csv_log(struct ast_cdr *cdr)
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
+
 	loaded = 0;
 	return 0;
 }
diff --git a/cdr/cdr_custom.c b/cdr/cdr_custom.c
index 2a3b1a1dd0..51235db7fd 100644
--- a/cdr/cdr_custom.c
+++ b/cdr/cdr_custom.c
@@ -184,7 +184,9 @@ static int custom_log(struct ast_cdr *cdr)
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
 
 	if (AST_RWLIST_WRLOCK(&sinks)) {
 		ast_cdr_register(name, ast_module_info->description, custom_log);
diff --git a/cdr/cdr_manager.c b/cdr/cdr_manager.c
index e3ae7a57d2..90c5dbd231 100644
--- a/cdr/cdr_manager.c
+++ b/cdr/cdr_manager.c
@@ -86,8 +86,9 @@ static int load_config(int reload)
 	if (!cfg) {
 		/* Standard configuration */
 		ast_log(LOG_WARNING, "Failed to load configuration file. Module not activated.\n");
-		if (enablecdr)
-			ast_cdr_unregister(name);
+		if (enablecdr) {
+			ast_cdr_backend_suspend(name);
+		}
 		enablecdr = 0;
 		return -1;
 	}
@@ -135,10 +136,11 @@ static int load_config(int reload)
 
 	ast_config_destroy(cfg);
 
-	if (enablecdr && !newenablecdr)
-		ast_cdr_unregister(name);
-	else if (!enablecdr && newenablecdr)
-		ast_cdr_register(name, "Asterisk Manager Interface CDR Backend", manager_log);
+	if (!newenablecdr) {
+		ast_cdr_backend_suspend(name);
+	} else if (newenablecdr) {
+		ast_cdr_backend_unsuspend(name);
+	}
 	enablecdr = newenablecdr;
 
 	return 0;
@@ -210,7 +212,10 @@ static int manager_log(struct ast_cdr *cdr)
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
+
 	if (customfields)
 		ast_free(customfields);
 
@@ -219,7 +224,12 @@ static int unload_module(void)
 
 static int load_module(void)
 {
+	if (ast_cdr_register(name, "Asterisk Manager Interface CDR Backend", manager_log)) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
 	if (load_config(0)) {
+		ast_cdr_unregister(name);
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
diff --git a/cdr/cdr_odbc.c b/cdr/cdr_odbc.c
index 022d75210e..be07a8a56b 100644
--- a/cdr/cdr_odbc.c
+++ b/cdr/cdr_odbc.c
@@ -266,8 +266,10 @@ static int odbc_load_module(int reload)
 	} while (0);
 
 	if (ast_test_flag(&config, CONFIG_REGISTERED) && (!cfg || dsn == NULL || table == NULL)) {
-		ast_cdr_unregister(name);
+		ast_cdr_backend_suspend(name);
 		ast_clear_flag(&config, CONFIG_REGISTERED);
+	} else {
+		ast_cdr_backend_unsuspend(name);
 	}
 
 	if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg != CONFIG_STATUS_FILEINVALID) {
@@ -283,7 +285,9 @@ static int load_module(void)
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
 
 	if (dsn) {
 		ast_verb(11, "cdr_odbc: free dsn\n");
diff --git a/cdr/cdr_pgsql.c b/cdr/cdr_pgsql.c
index dc73de4779..6ac3897884 100644
--- a/cdr/cdr_pgsql.c
+++ b/cdr/cdr_pgsql.c
@@ -436,7 +436,10 @@ static void empty_columns(void)
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
+
 	ast_cli_unregister_multiple(cdr_pgsql_status_cli, ARRAY_LEN(cdr_pgsql_status_cli));
 
 	PQfinish(conn);
diff --git a/cdr/cdr_radius.c b/cdr/cdr_radius.c
index 2bf2002feb..1466808d5b 100644
--- a/cdr/cdr_radius.c
+++ b/cdr/cdr_radius.c
@@ -230,7 +230,10 @@ return_cleanup:
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
+
 	if (rh) {
 		rc_destroy(rh);
 		rh = NULL;
diff --git a/cdr/cdr_sqlite.c b/cdr/cdr_sqlite.c
index 46aa42bb1a..884837c162 100644
--- a/cdr/cdr_sqlite.c
+++ b/cdr/cdr_sqlite.c
@@ -191,7 +191,10 @@ static int sqlite_log(struct ast_cdr *cdr)
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
+
 	if (db) {
 		sqlite_close(db);
 	}
diff --git a/cdr/cdr_sqlite3_custom.c b/cdr/cdr_sqlite3_custom.c
index 6012346042..83dac6a5d6 100644
--- a/cdr/cdr_sqlite3_custom.c
+++ b/cdr/cdr_sqlite3_custom.c
@@ -289,7 +289,9 @@ static int write_cdr(struct ast_cdr *cdr)
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
 
 	free_config(0);
 
diff --git a/cdr/cdr_syslog.c b/cdr/cdr_syslog.c
index dec4d65e9d..de8cae4eca 100644
--- a/cdr/cdr_syslog.c
+++ b/cdr/cdr_syslog.c
@@ -235,7 +235,9 @@ static int load_config(int reload)
 
 static int unload_module(void)
 {
-	ast_cdr_unregister(name);
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
 
 	if (AST_RWLIST_WRLOCK(&sinks)) {
 		ast_cdr_register(name, ast_module_info->description, syslog_log);
diff --git a/cdr/cdr_tds.c b/cdr/cdr_tds.c
index aef57b55d1..5a1312eccd 100644
--- a/cdr/cdr_tds.c
+++ b/cdr/cdr_tds.c
@@ -443,6 +443,10 @@ failed:
 
 static int tds_unload_module(void)
 {
+	if (ast_cdr_unregister(name)) {
+		return -1;
+	}
+
 	if (settings) {
 		ast_mutex_lock(&tds_lock);
 		mssql_disconnect();
@@ -452,8 +456,6 @@ static int tds_unload_module(void)
 		ast_free(settings);
 	}
 
-	ast_cdr_unregister(name);
-
 	dbexit();
 
 	return 0;
diff --git a/include/asterisk/cdr.h b/include/asterisk/cdr.h
index 49acc61dd2..801b1b4989 100644
--- a/include/asterisk/cdr.h
+++ b/include/asterisk/cdr.h
@@ -503,8 +503,27 @@ int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be);
  * \brief Unregister a CDR handling engine
  * \param name name of CDR handler to unregister
  * Unregisters a CDR by it's name
+ *
+ * \retval 0 The backend unregistered successfully
+ * \retval -1 The backend could not be unregistered at this time
+ */
+int ast_cdr_unregister(const char *name);
+
+/*!
+ * \brief Suspend a CDR backend temporarily
+ *
+  * \retval 0 The backend is suspdended
+  * \retval -1 The backend could not be suspended
+  */
+int ast_cdr_backend_suspend(const char *name);
+
+/*!
+ * \brief Unsuspend a CDR backend
+ *
+ * \retval 0 The backend was unsuspended
+ * \retval -1 The back could not be unsuspended
  */
-void ast_cdr_unregister(const char *name);
+int ast_cdr_backend_unsuspend(const char *name);
 
 /*!
  * \brief Disposition to a string
diff --git a/main/cdr.c b/main/cdr.c
index d4c2b96ab0..02056ecc51 100644
--- a/main/cdr.c
+++ b/main/cdr.c
@@ -294,6 +294,7 @@ struct cdr_beitem {
 	char desc[80];
 	ast_cdrbe be;
 	AST_RWLIST_ENTRY(cdr_beitem) list;
+	int suspended:1;
 };
 
 /*! \brief List of registered backends */
@@ -2581,6 +2582,42 @@ int ast_cdr_is_enabled(void)
 	return ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED);
 }
 
+int ast_cdr_backend_suspend(const char *name)
+{
+	int success = -1;
+	struct cdr_beitem *i = NULL;
+
+	AST_RWLIST_WRLOCK(&be_list);
+	AST_RWLIST_TRAVERSE(&be_list, i, list) {
+		if (!strcasecmp(name, i->name)) {
+			ast_debug(3, "Suspending CDR backend %s\n", i->name);
+			i->suspended = 1;
+			success = 0;
+		}
+	}
+	AST_RWLIST_UNLOCK(&be_list);
+
+	return success;
+}
+
+int ast_cdr_backend_unsuspend(const char *name)
+{
+	int success = -1;
+	struct cdr_beitem *i = NULL;
+
+	AST_RWLIST_WRLOCK(&be_list);
+	AST_RWLIST_TRAVERSE(&be_list, i, list) {
+		if (!strcasecmp(name, i->name)) {
+			ast_debug(3, "Unsuspending CDR backend %s\n", i->name);
+			i->suspended = 0;
+			success = 0;
+		}
+	}
+	AST_RWLIST_UNLOCK(&be_list);
+
+	return success;
+}
+
 int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
 {
 	struct cdr_beitem *i = NULL;
@@ -2615,24 +2652,39 @@ int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
 	return 0;
 }
 
-void ast_cdr_unregister(const char *name)
+int ast_cdr_unregister(const char *name)
 {
-	struct cdr_beitem *i = NULL;
+	struct cdr_beitem *match = NULL;
+	int active_count;
 
 	AST_RWLIST_WRLOCK(&be_list);
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
-		if (!strcasecmp(name, i->name)) {
-			AST_RWLIST_REMOVE_CURRENT(list);
+	AST_RWLIST_TRAVERSE(&be_list, match, list) {
+		if (!strcasecmp(name, match->name)) {
 			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);
+	if (!match) {
+		AST_RWLIST_UNLOCK(&be_list);
+		return 0;
+	}
+
+	active_count = ao2_container_count(active_cdrs_by_channel);
+
+	if (!match->suspended && active_count != 0) {
+		AST_RWLIST_UNLOCK(&be_list);
+		ast_log(AST_LOG_WARNING, "Unable to unregister CDR backend %s; %d CDRs are still active\n",
+			name, active_count);
+		return -1;
 	}
+
+	AST_RWLIST_REMOVE(&be_list, match, list);
+	AST_RWLIST_UNLOCK(&be_list);
+
+	ast_verb(2, "Unregistered '%s' CDR backend\n", name);
+	ast_free(match);
+
+	return 0;
 }
 
 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
@@ -3159,7 +3211,9 @@ static void post_cdr(struct ast_cdr *cdr)
 		}
 		AST_RWLIST_RDLOCK(&be_list);
 		AST_RWLIST_TRAVERSE(&be_list, i, list) {
-			i->be(cdr);
+			if (!i->suspended) {
+				i->be(cdr);
+			}
 		}
 		AST_RWLIST_UNLOCK(&be_list);
 	}
@@ -3772,7 +3826,7 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
 			ast_cli(a->fd, "    (none)\n");
 		} else {
 			AST_RWLIST_TRAVERSE(&be_list, beitem, list) {
-				ast_cli(a->fd, "    %s\n", beitem->name);
+				ast_cli(a->fd, "    %s%s\n", beitem->name, beitem->suspended ? " (suspended) " : "");
 			}
 		}
 		AST_RWLIST_UNLOCK(&be_list);
-- 
GitLab