MINOR: map/acl: add the possibility to specify the version in "clear map/acl"

This will ease maintenance of versionned maps by allowing to clear old or
failed updates instead of the current version. Nothing was done to allow
clearing everyhing, though if there was a need for this, implementing "@all"
or something equivalent wouldn't require more than 3 lines of code.
diff --git a/doc/management.txt b/doc/management.txt
index c15e71a..6ccd468 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -1495,15 +1495,19 @@
   server. This has the same effect as restarting. This command is restricted
   and can only be issued on sockets configured for level "admin".
 
-clear acl <acl>
+clear acl [@<ver>] <acl>
   Remove all entries from the acl <acl>. <acl> is the #<id> or the <file>
   returned by "show acl". Note that if the reference <acl> is a file and is
-  shared with a map, this map will be also cleared.
+  shared with a map, this map will be also cleared. By default only the current
+  version of the ACL is cleared (the one being matched against). However it is
+  possible to specify another version using '@' followed by this version.
 
-clear map <map>
+clear map [@<ver>] <map>
   Remove all entries from the map <map>. <map> is the #<id> or the <file>
   returned by "show map". Note that if the reference <map> is a file and is
-  shared with a acl, this acl will be also cleared.
+  shared with a acl, this acl will be also cleared. By default only the current
+  version of the map is cleared (the one being matched against). However it is
+  possible to specify another version using '@' followed by this version.
 
 clear table <table> [ data.<type> <operator> <value> ] | [ key <key> ]
   Remove entries from the stick-table <table>.
diff --git a/src/map.c b/src/map.c
index 080fee8..42b3bdc 100644
--- a/src/map.c
+++ b/src/map.c
@@ -935,15 +935,17 @@
 	return 1;
 }
 
-
-/* continue to clear a map which was started in the parser */
+/* continue to clear a map which was started in the parser. The range of
+ * generations this applies to is taken from appctx->ctx.cli.i0 for the oldest
+ * and appctx->ctx.cli.i1 for the latest.
+ */
 static int cli_io_handler_clear_map(struct appctx *appctx)
 {
 	struct stream_interface *si = appctx->owner;
 	int finished;
 
 	HA_SPIN_LOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock);
-	finished = pat_ref_prune(appctx->ctx.map.ref);
+	finished = pat_ref_purge_range(appctx->ctx.map.ref, appctx->ctx.cli.i0, appctx->ctx.cli.i1, 100);
 	HA_SPIN_UNLOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock);
 
 	if (!finished) {
@@ -954,15 +956,30 @@
 	return 1;
 }
 
+/* note: sets appctx->ctx.cli.i0 and appctx->ctx.cli.i1 to the oldest and
+ * latest generations to clear, respectively, and will call the clear_map
+ * handler.
+ */
 static int cli_parse_clear_map(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (strcmp(args[1], "map") == 0 || strcmp(args[1], "acl") == 0) {
+		const char *gen = NULL;
+
 		/* Set ACL or MAP flags. */
 		if (args[1][0] == 'm')
 			appctx->ctx.map.display_flags = PAT_REF_MAP;
 		else
 			appctx->ctx.map.display_flags = PAT_REF_ACL;
 
+		/* For both "map" and "acl" we may have an optional generation
+		 * number specified using a "@" character before the pattern
+		 * file name.
+		 */
+		if (*args[2] == '@') {
+			gen = args[2] + 1;
+			args++;
+		}
+
 		/* no parameter */
 		if (!*args[2]) {
 			if (appctx->ctx.map.display_flags == PAT_REF_MAP)
@@ -981,6 +998,12 @@
 				return cli_err(appctx, "Unknown ACL identifier. Please use #<id> or <file>.\n");
 		}
 
+		/* set the desired generation id in cli.i0/i1 */
+		if (gen)
+			appctx->ctx.cli.i1 = appctx->ctx.cli.i0 = str2uic(gen);
+		else
+			appctx->ctx.cli.i1 = appctx->ctx.cli.i0 = appctx->ctx.map.ref->curr_gen;
+
 		/* delegate the clearing to the I/O handler which can yield */
 		return 0;
 	}
@@ -991,12 +1014,12 @@
 
 static struct cli_kw_list cli_kws = {{ },{
 	{ { "add",   "acl", NULL }, "add acl        : add acl entry", cli_parse_add_map, NULL },
-	{ { "clear", "acl", NULL }, "clear acl <id> : clear the content of this acl", cli_parse_clear_map, cli_io_handler_clear_map, NULL },
+	{ { "clear", "acl", NULL }, "clear acl [@ver] <id> : clear the content of this acl", cli_parse_clear_map, cli_io_handler_clear_map, NULL },
 	{ { "del",   "acl", NULL }, "del acl        : delete acl entry", cli_parse_del_map, NULL },
 	{ { "get",   "acl", NULL }, "get acl        : report the patterns matching a sample for an ACL", cli_parse_get_map, cli_io_handler_map_lookup, cli_release_mlook },
 	{ { "show",  "acl", NULL }, "show acl [@ver] [id] : report available acls or dump an acl's contents", cli_parse_show_map, NULL },
 	{ { "add",   "map", NULL }, "add map        : add map entry", cli_parse_add_map, NULL },
-	{ { "clear", "map", NULL }, "clear map <id> : clear the content of this map", cli_parse_clear_map, cli_io_handler_clear_map, NULL },
+	{ { "clear", "map", NULL }, "clear map [@ver] <id> : clear the content of this map", cli_parse_clear_map, cli_io_handler_clear_map, NULL },
 	{ { "del",   "map", NULL }, "del map        : delete map entry", cli_parse_del_map, NULL },
 	{ { "get",   "map", NULL }, "get map        : report the keys and values matching a sample for a map", cli_parse_get_map, cli_io_handler_map_lookup, cli_release_mlook },
 	{ { "set",   "map", NULL }, "set map        : modify map entry", cli_parse_set_map, NULL },