--- asterisk-trunk/apps/app_queue.c	2011-03-24 16:57:40.428555719 +0100
+++ asterisk-trunk-patched/apps/app_queue.c	2011-04-21 10:47:44.688974735 +0200
@@ -789,6 +789,17 @@
 		<description>
 		</description>
 	</manager>
+	<manager name="QueueGroupShow" language="en_US">
+		<synopsis>
+			Show Queue Groups.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Group" />
+		</syntax>
+		<description>
+		</description>
+	</manager>
 	<manager name="QueueReload" language="en_US">
 		<synopsis>
 			Reload a queue, queues, or any sub-section of a queue or queues.
@@ -1024,6 +1035,7 @@
 	AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */
 	struct penalty_rule *pr;               /*!< Pointer to the next penalty rule to implement */
 	struct queue_ent *next;                /*!< The next queue entry */
+	AST_LIST_ENTRY(queue_ent) list;
 };
 
 struct member {
@@ -1174,11 +1186,25 @@
 	 *       in, so this can not simply be replaced with ao2_container_count(). 
 	 */
 	int membercount;
+	
+	int group_found;                    /*!< How to tell if a group has been specified for a queue */
+	int group_strategy_found;			/*!< How to tell if a queue's group specifies the strategy to use */
+	struct queue_group *group;          /*!< To which group does this queue belong */
+
 	struct queue_ent *head;             /*!< Head of the list of callers */
 	AST_LIST_ENTRY(call_queue) list;    /*!< Next call queue */
 	AST_LIST_HEAD_NOLOCK(, penalty_rule) rules; /*!< The list of penalty rules to invoke */
 };
 
+struct queue_group {
+	AST_LIST_HEAD(, queue_ent) callers;
+	AST_LIST_ENTRY(queue_group) list;
+	char name[80];
+	int unused;                         /*!< If a group is removed from configuration, this is how we detect it */
+};
+
+static AST_LIST_HEAD_STATIC(groups, queue_group);
+
 struct rule_list {
 	char name[80];
 	AST_LIST_HEAD_NOLOCK(,penalty_rule) rules;
@@ -1313,31 +1339,6 @@
 	}
 }
 
-/*! \brief Insert the 'new' entry after the 'prev' entry of queue 'q' */
-static inline void insert_entry(struct call_queue *q, struct queue_ent *prev, struct queue_ent *new, int *pos)
-{
-	struct queue_ent *cur;
-
-	if (!q || !new)
-		return;
-	if (prev) {
-		cur = prev->next;
-		prev->next = new;
-	} else {
-		cur = q->head;
-		q->head = new;
-	}
-	new->next = cur;
-
-	/* every queue_ent must have a reference to it's parent call_queue, this
-	 * reference does not go away until the end of the queue_ent's life, meaning
-	 * that even when the queue_ent leaves the call_queue this ref must remain. */
-	queue_ref(q);
-	new->parent = q;
-	new->pos = ++(*pos);
-	new->opos = *pos;
-}
-
 /*! \brief Check if members are available
  *
  * This function checks to see if members are available to be called. If any member
@@ -1656,6 +1657,130 @@
 	return strcasecmp(mem1->interface, mem2->interface) ? 0 : CMP_MATCH | CMP_STOP;
 }
 
+static void set_strategy(struct call_queue *q, const char *strategy)
+{
+	q->strategy = strat2int(strategy);
+
+	if (q->strategy < 0) {
+		ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n", strategy, q->name);
+		q->strategy = QUEUE_STRATEGY_RINGALL;
+	}
+}
+
+static void reload_single_member(const char *memberdata, struct call_queue *q)
+{
+	char parse[80];
+	char *interface;
+	char *membername = NULL;
+	int penalty;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(interface);
+		AST_APP_ARG(penalty);
+		AST_APP_ARG(membername);
+	);
+	struct member *newm = NULL, *cur = NULL;
+	struct member tmpmem;
+	char *tmp;
+
+	/* Add a new member */
+	ast_copy_string(parse, memberdata, sizeof(parse));
+			
+	AST_NONSTANDARD_APP_ARGS(args, parse, ',');
+
+	interface = args.interface;
+	if (!ast_strlen_zero(args.penalty)) {
+		tmp = args.penalty;
+		while (*tmp && *tmp < 33) tmp++;
+		penalty = atoi(tmp);
+		if (penalty < 0) {
+			penalty = 0;
+		}
+	} else
+		penalty = 0;
+
+	if (!ast_strlen_zero(args.membername)) {
+		membername = args.membername;
+		while (*membername && *membername < 33) membername++;
+	} else
+		membername = interface;
+
+	/* Find the old position in the list */
+	ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
+	cur = ao2_find(q->members, &tmpmem, OBJ_POINTER | OBJ_UNLINK);
+
+	newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0, interface);
+	ao2_link(q->members, newm);
+	ao2_ref(newm, -1);
+	newm = NULL;
+
+	if (cur)
+		ao2_ref(cur, -1);
+	else {
+		q->membercount++;
+	}
+}
+
+static void parse_queuegroup(struct call_queue *q, const char *groupname)
+{
+	struct ast_config *groupcfg = NULL;
+	struct ast_variable *var = NULL, *realtimevar = NULL;
+	struct queue_group *group = NULL;
+        struct ast_flags config_flags = { 0 };
+
+	/* As with other queue-related configurations, static trumps realtime.
+	 * In other words, if we find the specified group in the config file, then
+	 * we use it. We only look in realtime if we can't find the group in the config file
+	 */
+	if (!(groupcfg = ast_config_load("queuegroups.conf", config_flags))) {
+		if (!(realtimevar = ast_load_realtime("queue_groups", "name", groupname, NULL))) {
+			ast_log(LOG_WARNING, "Group %s was specified for queue %s but could not find definition. Ignoring!\n", groupname, q->name);
+			return;
+		}
+	}
+
+	for (var = realtimevar ? realtimevar : ast_variable_browse(groupcfg, groupname); var; var = var->next) {
+		if (!strcasecmp(var->name, "member")) {
+			reload_single_member(var->value, q);
+		} else if (!strcasecmp(var->name, "strategy")) {
+			if (!q->group_strategy_found){
+				set_strategy(q, var->value);
+				q->group_strategy_found = 1;
+			} else {
+				ast_log(LOG_WARNING, "Ignoring strategy parameter for queue %s since its group specifies a strategy already\n", q->name);
+			}
+		} else {
+			ast_log(LOG_WARNING, "Invalid queuegroup option '%s' for queuegroup '%s'\n", var->name, groupname);
+		}
+	}
+
+	if (groupcfg) {
+		ast_config_destroy(groupcfg);
+	} else {
+		ast_variables_destroy(realtimevar);
+	}
+
+	AST_LIST_TRAVERSE(&groups, group, list) {
+		if (!strcasecmp(group->name, groupname)) {
+			group->unused = 0;
+			q->group = group;
+			q->group_found = 1;
+			return;
+		}
+	}
+
+	/* This is a new group */
+	if (!(group = ast_calloc(1, sizeof (*group)))) {
+		/* OH SHIT */
+	}
+	ast_copy_string(group->name, groupname, sizeof(group->name));
+	AST_LIST_HEAD_INIT(&group->callers);
+	group->unused = 0;
+	q->group = group;
+	q->group_found = 1;
+	AST_LIST_INSERT_HEAD(&groups, group, list);
+}
+
+
 /*! 
  * \brief Initialize Queue default values.
  * \note the queue's lock  must be held before executing this function
@@ -1665,6 +1790,8 @@
 	int i;
 	struct penalty_rule *pr_iter;
 
+	q->group_found = 0;
+	q->group_strategy_found = 0;
 	q->dead = 0;
 	q->retry = DEFAULT_RETRY;
 	q->timeout = DEFAULT_TIMEOUT;
@@ -2052,6 +2179,12 @@
 		q->timeoutrestart = ast_true(val);
 	} else if (!strcasecmp(param, "defaultrule")) {
 		ast_string_field_set(q, defaultrule, val);
+	} else if (!strcasecmp(param, "group")) {
+		if (!q->group_found) {
+			parse_queuegroup(q, val);
+		} else {
+			ast_log(LOG_WARNING, "Ignoring group setting %s for queue %s since a group was already specified\n", val, q->name);
+		}		
 	} else if (!strcasecmp(param, "timeoutpriority")) {
 		if (!strcasecmp(val, "conf")) {
 			q->timeoutpriority = TIMEOUT_PRIORITY_CONF;
@@ -2453,7 +2586,7 @@
 static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason, int position)
 {
 	struct call_queue *q;
-	struct queue_ent *cur, *prev = NULL;
+	struct queue_ent *cur;
 	int res = -1;
 	int pos = 0;
 	int inserted = 0;
@@ -2481,34 +2614,50 @@
 		 * the queue.
 		 * Take into account the priority of the calling user */
 		inserted = 0;
-		prev = NULL;
-		cur = q->head;
-		while (cur) {
+
+		AST_LIST_LOCK(&q->group->callers);
+		AST_LIST_TRAVERSE_SAFE_BEGIN(&q->group->callers, cur, list) {
 			/* We have higher priority than the current user, enter
 			 * before him, after all the other users with priority
 			 * higher or equal to our priority. */
 			if ((!inserted) && (qe->prio > cur->prio)) {
-				insert_entry(q, prev, qe, &pos);
+				AST_LIST_INSERT_BEFORE_CURRENT(qe, list);
+				if (option_debug > 2)
+					ast_log(LOG_DEBUG, "Inserted %s into group %s, address %p\n", qe->chan->name, q->group->name, q->group);
+				qe->parent = q;
+				qe->pos = ++pos;
+				qe->opos = pos;
 				inserted = 1;
 			}
 			/* <= is necessary for the position comparison because it may not be possible to enter
 			 * at our desired position since higher-priority callers may have taken the position we want
 			 */
 			if (!inserted && (qe->prio >= cur->prio) && position && (position <= pos + 1)) {
-				insert_entry(q, prev, qe, &pos);
-				/*pos is incremented inside insert_entry, so don't need to add 1 here*/
+				AST_LIST_INSERT_BEFORE_CURRENT(qe, list);
+				qe->parent = q;
+				qe->pos = ++pos;
+				qe->opos = pos;
+				
 				if (position < pos) {
 					ast_log(LOG_NOTICE, "Asked to be inserted at position %d but forced into position %d due to higher priority callers\n", position, pos);
 				}
 				inserted = 1;
 			}
 			cur->pos = ++pos;
-			prev = cur;
-			cur = cur->next;
 		}
+		AST_LIST_TRAVERSE_SAFE_END;
+		
 		/* No luck, join at the end of the queue */
-		if (!inserted)
-			insert_entry(q, prev, qe, &pos);
+		if (!inserted) {
+			AST_LIST_INSERT_TAIL(&q->group->callers, qe, list);
+			if (option_debug > 2)
+				ast_log(LOG_DEBUG, "Inserted %s into group %s, address %p\n", qe->chan->name, q->group->name, q->group);
+			qe->parent = q;
+			qe->pos = ++pos;
+			qe->opos = pos;
+		}
+		AST_LIST_UNLOCK(&q->group->callers);
+
 		ast_copy_string(qe->moh, q->moh, sizeof(qe->moh));
 		ast_copy_string(qe->announce, q->announce, sizeof(qe->announce));
 		ast_copy_string(qe->context, q->context, sizeof(qe->context));
@@ -2766,7 +2915,8 @@
 	ao2_lock(q);
 
 	prev = NULL;
-	for (current = q->head; current; current = current->next) {
+	AST_LIST_LOCK(&q->group->callers);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&q->group->callers, current, list) {
 		if (current == qe) {
 			char posstr[20];
 			q->count--;
@@ -2777,10 +2927,7 @@
 				qe->chan->name, q->name,  q->count, qe->pos, qe->chan->uniqueid);
 			ast_debug(1, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name );
 			/* Take us out of the queue */
-			if (prev)
-				prev->next = current->next;
-			else
-				q->head = current->next;
+			AST_LIST_REMOVE_CURRENT(list);
 			/* Free penalty rules */
 			while ((pr_iter = AST_LIST_REMOVE_HEAD(&qe->qe_rules, list)))
 				ast_free(pr_iter);
@@ -2792,6 +2939,9 @@
 			prev = current;
 		}
 	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_UNLOCK(&q->group->callers);
+	
 	ao2_unlock(q);
 
 	/*If the queue is a realtime queue, check to see if it's still defined in real time*/
@@ -3854,11 +4004,16 @@
 
 	ast_debug(1, "There %s %d available %s.\n", avl != 1 ? "are" : "is", avl, avl != 1 ? "members" : "member");
 
-	while ((idx < avl) && (ch) && (ch != qe)) {
-		if (!ch->pending)
-			idx++;
-		ch = ch->next;			
+	AST_LIST_LOCK(&qe->parent->group->callers);
+	AST_LIST_TRAVERSE(&qe->parent->group->callers, ch, list) {
+		if (idx >= avl || ch == qe) {
+			break;
+		}
+		if (!ch->pending) {
+			idx++;		
+		}
 	}
+	AST_LIST_UNLOCK(&qe->parent->group->callers);
 
 	ao2_unlock(qe->parent);
 	/* If the queue entry is within avl [the number of available members] calls from the top ... 
@@ -6524,80 +6679,6 @@
 		shared_lastcall = ast_true(general_val);
 }
 
-/*! \brief reload information pertaining to a single member
- *
- * This function is called when a member = line is encountered in
- * queues.conf.
- *
- * \param memberdata The part after member = in the config file
- * \param q The queue to which this member belongs
- */
-static void reload_single_member(const char *memberdata, struct call_queue *q)
-{
-	char *membername, *interface, *state_interface, *tmp;
-	char *parse;
-	struct member *cur, *newm;
-	struct member tmpmem;
-	int penalty;
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(interface);
-		AST_APP_ARG(penalty);
-		AST_APP_ARG(membername);
-		AST_APP_ARG(state_interface);
-	);
-
-	if (ast_strlen_zero(memberdata)) {
-		ast_log(LOG_WARNING, "Empty queue member definition. Moving on!\n");
-		return;
-	}
-
-	/* Add a new member */
-	parse = ast_strdupa(memberdata);
-				
-	AST_STANDARD_APP_ARGS(args, parse);
-
-	interface = args.interface;
-	if (!ast_strlen_zero(args.penalty)) {
-		tmp = args.penalty;
-		ast_strip(tmp);
-		penalty = atoi(tmp);
-		if (penalty < 0) {
-			penalty = 0;
-		}
-	} else {
-		penalty = 0;
-	}
-
-	if (!ast_strlen_zero(args.membername)) {
-		membername = args.membername;
-		ast_strip(membername);
-	} else {
-		membername = interface;
-	}
-
-	if (!ast_strlen_zero(args.state_interface)) {
-		state_interface = args.state_interface;
-		ast_strip(state_interface);
-	} else {
-		state_interface = interface;
-	}
-
-	/* Find the old position in the list */
-	ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
-	cur = ao2_find(q->members, &tmpmem, OBJ_POINTER | OBJ_UNLINK);
-	if ((newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0, state_interface))) {
-		ao2_link(q->members, newm);
-		ao2_ref(newm, -1);
-	}
-	newm = NULL;
-
-	if (cur) {
-		ao2_ref(cur, -1);
-	} else {
-		q->membercount++;
-	}
-}
-
 static int mark_member_dead(void *obj, void *arg, int flags)
 {
 	struct member *member = obj;
@@ -6640,6 +6721,7 @@
  */
 static void reload_single_queue(struct ast_config *cfg, struct ast_flags *mask, const char *queuename)
 {
+	struct queue_group *group = NULL;
 	int new;
 	struct call_queue *q = NULL;
 	/*We're defining a queue*/
@@ -6704,6 +6786,12 @@
 		q->membercount = 0;
 		ao2_callback(q->members, OBJ_NODATA, mark_member_dead, NULL);
 	}
+	
+	/* And mark the queue groups as unused */
+	AST_LIST_TRAVERSE(&groups, group, list) {
+		group->unused = 1;
+	}
+	
 	for (var = ast_variable_browse(cfg, queuename); var; var = var->next) {
 		if (member_reload && !strcasecmp(var->name, "member")) {
 			reload_single_member(var->value, q);
@@ -6711,6 +6799,24 @@
 			queue_set_param(q, var->name, var->value, var->lineno, 1);
 		}
 	}
+	
+	if (!q->group_found) {
+		/* No group was specified for this queue, so we need to
+		 * figure out if the queue used to be part of a group and if
+		 * so, was it a named one.
+		 */
+		if (!q->group || !ast_strlen_zero(q->group->name)) {
+			/* Either this is a new queue or the queue was in a group
+			 * but now is not
+			 */
+			group = ast_calloc(1, sizeof(*group));
+			*(group->name) = '\0';
+			AST_LIST_HEAD_INIT(&group->callers);
+			q->group = group;
+		}
+	}
+	
+	
 	/* At this point, we've determined if the queue has a weight, so update use_weight
 	 * as appropriate
 	 */
@@ -6731,6 +6837,15 @@
 	} else {
 		ao2_unlock(q);
 	}
+	
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&groups, group, list) {
+		if (group->unused) {
+			AST_LIST_REMOVE_CURRENT(list);
+			free(group);
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	
 	queue_t_unref(q, "Expiring creation reference");
 }
 
@@ -6812,6 +6927,7 @@
 		ao2_callback(queues, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, kill_dead_queues, (char *) queuename);
 	}
 	ao2_unlock(queues);
+
 	return 0;
 }
   
@@ -6887,7 +7003,7 @@
  * List the queues strategy, calls processed, members logged in,
  * other queue statistics such as avg hold time.
 */
-static char *__queues_show(struct mansession *s, int fd, int argc, const char * const *argv)
+static char *__queues_show(struct mansession *s, int fd, int argc, const char * const *argv, int bygroup, const char *groupname)
 {
 	struct call_queue *q;
 	struct ast_str *out = ast_str_alloca(240);
@@ -6895,33 +7011,45 @@
 	time_t now = time(NULL);
 	struct ao2_iterator queue_iter;
 	struct ao2_iterator mem_iter;
-
-	if (argc != 2 && argc != 3)
-		return CLI_SHOWUSAGE;
-
-	if (argc == 3)	{ /* specific queue */
-		if ((q = load_realtime_queue(argv[2]))) {
-			queue_t_unref(q, "Done with temporary pointer");
-		}
-	} else if (ast_check_realtime("queues")) {
-		/* This block is to find any queues which are defined in realtime but
-		 * which have not yet been added to the in-core container
-		 */
-		struct ast_config *cfg = ast_load_realtime_multientry("queues", "name LIKE", "%", SENTINEL);
-		char *queuename;
-		if (cfg) {
-			for (queuename = ast_category_browse(cfg, NULL); !ast_strlen_zero(queuename); queuename = ast_category_browse(cfg, queuename)) {
-				if ((q = load_realtime_queue(queuename))) {
-					queue_t_unref(q, "Done with temporary pointer");
+	//char *groupname = ""; //NULL;
+	
+	if (bygroup == 0) {
+		if (argc != 2 && argc != 3)
+			return CLI_SHOWUSAGE;
+
+		if (argc == 3)	{ /* specific queue */
+			if ((q = load_realtime_queue(argv[2]))) {
+				queue_t_unref(q, "Done with temporary pointer");
+			}
+		} else if (ast_check_realtime("queues")) {
+			/* This block is to find any queues which are defined in realtime but
+			 * which have not yet been added to the in-core container
+			 */
+			struct ast_config *cfg = ast_load_realtime_multientry("queues", "name LIKE", "%", SENTINEL);
+			char *queuename;
+			if (cfg) {
+				for (queuename = ast_category_browse(cfg, NULL); !ast_strlen_zero(queuename); queuename = ast_category_browse(cfg, queuename)) {
+					if ((q = load_realtime_queue(queuename))) {
+						queue_t_unref(q, "Done with temporary pointer");
+					}
 				}
+				ast_config_destroy(cfg);
 			}
-			ast_config_destroy(cfg);
 		}
-	}
+	} else {
+		if (argc != 3 && argc != 4)
+			return CLI_SHOWUSAGE;
 
+		if (argc == 4)
+			groupname = argv[3];
+ 	}
+		
 	queue_iter = ao2_iterator_init(queues, AO2_ITERATOR_DONTLOCK);
 	ao2_lock(queues);
 	while ((q = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
+		if (bygroup != 0 && strcasecmp(q->group->name, groupname) != 0)
+			continue;
+			
 		float sl;
 		struct call_queue *realtime_queue = NULL;
 
@@ -6937,7 +7065,7 @@
 		} else if (q->realtime) {
 			queue_t_unref(realtime_queue, "Queue is already in memory");
 		}
-		if (argc == 3 && strcasecmp(q->name, argv[2])) {
+		if (bygroup == 0 && argc == 3 && strcasecmp(q->name, argv[2])) {
 			ao2_unlock(q);
 			queue_t_unref(q, "Done with iterator");
 			continue;
@@ -6985,20 +7113,24 @@
 			}
 			ao2_iterator_destroy(&mem_iter);
 		}
-		if (!q->head)
+		AST_LIST_LOCK(&q->group->callers);
+		if (AST_LIST_EMPTY(&q->group->callers))
 			do_print(s, fd, "   No Callers");
 		else {
 			struct queue_ent *qe;
 			int pos = 1;
 
 			do_print(s, fd, "   Callers: ");
-			for (qe = q->head; qe; qe = qe->next) {
-				ast_str_set(&out, 0, "      %d. %s (wait: %ld:%2.2ld, prio: %d)",
-					pos++, qe->chan->name, (long) (now - qe->start) / 60,
-					(long) (now - qe->start) % 60, qe->prio);
-				do_print(s, fd, ast_str_buffer(out));
+			AST_LIST_TRAVERSE(&q->group->callers, qe, list) {
+				if (qe->parent == q) { 
+					ast_str_set(&out, 0, "      %d. %s (wait: %ld:%2.2ld, prio: %d)",
+						pos++, qe->chan->name, (long) (now - qe->start) / 60,
+						(long) (now - qe->start) % 60, qe->prio);
+					do_print(s, fd, ast_str_buffer(out));
+				}
 			}
 		}
+		AST_LIST_UNLOCK(&q->group->callers);
 		do_print(s, fd, "");	/* blank line between entries */
 		ao2_unlock(q);
 		queue_t_unref(q, "Done with iterator"); /* Unref the iterator's reference */
@@ -7006,7 +7138,7 @@
 	ao2_iterator_destroy(&queue_iter);
 	ao2_unlock(queues);
 	if (!found) {
-		if (argc == 3)
+		if (bygroup == 0 && argc == 3)
 			ast_str_set(&out, 0, "No such queue: %s.", argv[2]);
 		else
 			ast_str_set(&out, 0, "No queues.");
@@ -7057,7 +7189,7 @@
 		return complete_queue_show(a->line, a->word, a->pos, a->n);	
 	}
 
-	return __queues_show(NULL, a->fd, a->argc, a->argv);
+	return __queues_show(NULL, a->fd, a->argc, a->argv, 0, "");
 }
 
 /*!\brief callback to display queues status in manager
@@ -7067,12 +7199,40 @@
 {
 	static const char * const a[] = { "queue", "show" };
 
-	__queues_show(s, -1, 2, a);
+	__queues_show(s, -1, 2, a, 0, "");	
 	astman_append(s, "\r\n\r\n");	/* Properly terminate Manager output */
 
 	return RESULT_SUCCESS;
 }
 
+/*!\brief callback to display queue group status in manager
+   \addtogroup Group_AMI
+ */
+static int manager_queue_group_show(struct mansession *s, const struct message *m)
+{
+	char *groupname = NULL;
+	struct queue_group *group_iter;
+	time_t now;
+	static const char * const a[] = { "queue", "group", "show" };
+
+	time(&now);
+
+	AST_LIST_LOCK(&groups);
+	AST_LIST_TRAVERSE(&groups, group_iter, list) {
+		if (ast_strlen_zero(groupname) || !strcasecmp(group_iter->name, groupname)) {
+
+			astman_append(s, "Groupname: %s\n", group_iter->name);
+			astman_append(s, "Queues:\n");
+			__queues_show(s, -1, 3, a, 1, group_iter->name);
+		}
+	}
+	AST_LIST_UNLOCK(&groups);
+	
+	astman_append(s, "\r\n\r\n");	/* Properly terminate Manager output */
+ 
+ 	return RESULT_SUCCESS;
+}
+
 static int manager_queue_rule_show(struct mansession *s, const struct message *m)
 {
 	const char *rule = astman_get_header(m, "Rule");
@@ -7240,7 +7400,8 @@
 			ao2_iterator_destroy(&mem_iter);
 			/* List Queue Entries */
 			pos = 1;
-			for (qe = q->head; qe; qe = qe->next) {
+			AST_LIST_LOCK(&q->group->callers);
+			AST_LIST_TRAVERSE(&q->group->callers, qe, list) {
 				astman_append(s, "Event: QueueEntry\r\n"
 					"Queue: %s\r\n"
 					"Position: %d\r\n"
@@ -7256,6 +7417,7 @@
 					S_COR(qe->chan->caller.id.name.valid, qe->chan->caller.id.name.str, "unknown"),
 					(long) (now - qe->start), idText);
 			}
+			AST_LIST_UNLOCK(&q->group->callers);
 		}
 		ao2_unlock(q);
 		queue_t_unref(q, "Done with iterator");
@@ -7270,6 +7432,7 @@
 	return RESULT_SUCCESS;
 }
 
+
 static int manager_add_queue_member(struct mansession *s, const struct message *m)
 {
 	const char *queuename, *interface, *penalty_s, *paused_s, *membername, *state_interface;
@@ -7954,6 +8117,67 @@
 	return CLI_SUCCESS;
 }
 
+static char *complete_queue_group_show(const char *line, const char *word, int pos, int state)
+{
+	struct queue_group *group;
+	char *ret = NULL;
+	int which = 0;
+	int wordlen = strlen(word);
+
+	if (pos != 3) {
+		ast_log(LOG_DEBUG, "Hitting this???, pos is %d\n", pos);
+		return NULL;
+	}
+	
+	AST_LIST_LOCK(&groups);
+	AST_LIST_TRAVERSE(&groups, group, list) {
+		if (!strncasecmp(word, group->name, wordlen) && ++which > state) {
+			ret = ast_strdup(group->name);	
+			break;
+		}
+	}
+	AST_LIST_UNLOCK(&groups);
+
+	return ret;
+}
+
+static char *handle_queue_group_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	const char *groupname;
+	struct queue_group *group_iter;
+	time_t now;
+
+	time(&now);
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "queue group show";
+		e->usage =
+		"Usage: queue group show [groupname]\n"
+		"       Shows information about group groupname. If no groupname is\n"
+		"       specified, then all groups are shown\n";
+		return NULL;
+	case CLI_GENERATE:
+		return complete_queue_group_show(a->line, a->word, a->pos, a->n);
+	}
+
+	if (a->argc != 3 && a->argc != 4)
+		return CLI_SHOWUSAGE;
+
+	groupname = a->argc == 4 ? a->argv[3] : "";
+	AST_LIST_LOCK(&groups);
+	AST_LIST_TRAVERSE(&groups, group_iter, list) {
+		if (ast_strlen_zero(groupname) || !strcasecmp(group_iter->name, groupname)) {
+			ast_cli(a->fd, "Groupname: %s\n", group_iter->name);
+			ast_cli(a->fd, "Queues:\n");
+			__queues_show(NULL, a->fd, a->argc, a->argv, 1, group_iter->name);
+		}
+	}
+	AST_LIST_UNLOCK(&groups);
+	return CLI_SUCCESS; 
+}
+
+
 static const char qpm_cmd_usage[] = 
 "Usage: queue pause member <channel> in <queue> reason <reason>\n";
 
@@ -7963,6 +8187,11 @@
 static const char qsmp_cmd_usage[] =
 "Usage: queue set member penalty <channel> from <queue> <penalty>\n";
 
+static const char qgs_cmd_usage[] =
+"Usage: queue group show [groupname]\n"
+"       Shows information about group groupname. If no groupname is\n"
+"       specified, then all groups are shown\n";
+
 static struct ast_cli_entry cli_queue[] = {
 	AST_CLI_DEFINE(queue_show, "Show status of a specified queue"),
 	AST_CLI_DEFINE(handle_queue_add_member, "Add a channel to a specified queue"),
@@ -7971,6 +8200,7 @@
 	AST_CLI_DEFINE(handle_queue_set_member_penalty, "Set penalty for a channel of a specified queue"),
 	AST_CLI_DEFINE(handle_queue_rule_show, "Show the rules defined in queuerules.conf"),
 	AST_CLI_DEFINE(handle_queue_reload, "Reload queues, members, queue rules, or parameters"),
+	AST_CLI_DEFINE(handle_queue_group_show, "Show status of specified queue group"),
 	AST_CLI_DEFINE(handle_queue_reset, "Reset statistics for a queue"),
 };
 
@@ -8255,6 +8485,7 @@
 	res |= ast_manager_unregister("QueueAdd");
 	res |= ast_manager_unregister("QueueRemove");
 	res |= ast_manager_unregister("QueuePause");
+	res |= ast_manager_unregister("QueueGroupShow");
 	res |= ast_manager_unregister("QueueLog");
 	res |= ast_manager_unregister("QueuePenalty");
 	res |= ast_unregister_application(app_aqm);
@@ -8335,6 +8566,7 @@
 	res |= ast_manager_register_xml("QueueLog", EVENT_FLAG_AGENT, manager_queue_log_custom);
 	res |= ast_manager_register_xml("QueuePenalty", EVENT_FLAG_AGENT, manager_queue_member_penalty);
 	res |= ast_manager_register_xml("QueueRule", 0, manager_queue_rule_show);
+	res |= ast_manager_register_xml("QueueGroupShow", 0, manager_queue_group_show);
 	res |= ast_manager_register_xml("QueueReload", 0, manager_queue_reload);
 	res |= ast_manager_register_xml("QueueReset", 0, manager_queue_reset);
 	res |= ast_custom_function_register(&queuevar_function);
