diff --git a/SOURCES/redhat-bugzilla-1541406.patch b/SOURCES/redhat-bugzilla-1541406.patch
new file mode 100644
index 0000000..9841486
--- /dev/null
+++ b/SOURCES/redhat-bugzilla-1541406.patch
@@ -0,0 +1,83 @@
+91dd4ae6b logutil: use $PCP_TMPFILE_DIR for intermediate pmlogger_merge archives
+b0c90d858 packaging: activate pmlogger_rewrite on upgrades
+b5e602187 packaging: revert pcp_archive_dir subsitution in build/rpm/GNUmakefile
+
+--- a/src/pmlogger/pmlogger_merge.sh	2018-06-18 16:24:25.000000000 +1000
++++ b/src/pmlogger/pmlogger_merge.sh	2020-06-11 13:10:57.401576513 +1000
+@@ -26,8 +26,9 @@
+ 
+ prog=`basename $0`
+ tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
++tmpmerge=`mktemp -d $PCP_TMPFILE_DIR/pcp.XXXXXXXXX` || exit 1
+ status=0
+-trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
++trap "rm -rf $tmp $tmpmerge; exit \$status" 0 1 2 3 15
+ 
+ force=false
+ VERBOSE=false
+@@ -229,8 +230,8 @@
+ 	    # output = 108 file descriptors which should be well below any
+ 	    # shell-imposed or system-imposed limits
+ 	    #
+-	    $VERBOSE && echo "		-> partial merge to $tmp/$part"
+-	    cmd="pmlogextract $list $tmp/$part"
++	    $VERBOSE && echo "		-> partial merge to $tmpmerge/$part"
++	    cmd="pmlogextract $list $tmpmerge/$part"
+ 	    if $SHOWME
+ 	    then
+ 		echo "+ $cmd"
+@@ -239,13 +240,13 @@
+ 		then
+ 		    :
+ 		else
+-		    $VERBOSE || echo "		-> partial merge to $tmp/$part"
++		    $VERBOSE || echo "		-> partial merge to $tmpmerge/$part"
+ 		    echo "$prog: Directory: `pwd`"
+-		    echo "$prog: Failed: pmlogextract $list $tmp/$part"
++		    echo "$prog: Failed: pmlogextract $list $tmpmerge/$part"
+ 		    _warning
+ 		fi
+ 	    fi
+-	    list=$tmp/$part
++	    list=$tmpmerge/$part
+ 	    part=`expr $part + 1`
+ 	    i=0
+ 	fi
+--- a/build/rpm/fedora.spec	2020-05-29 09:15:44.000000000 +1000
++++ b/build/rpm/fedora.spec	2020-06-11 13:10:57.402576528 +1000
+@@ -2814,6 +2814,7 @@
+ chown -R pcp:pcp %{_logsdir}/pmie 2>/dev/null
+ chown -R pcp:pcp %{_logsdir}/pmproxy 2>/dev/null
+ %{install_file "$PCP_PMNS_DIR" .NeedRebuild}
++%{install_file "$PCP_LOG_DIR/pmlogger" .NeedRewrite}
+ %if !%{disable_systemd}
+     %systemd_postun_with_restart pmcd.service
+     %systemd_post pmcd.service
+--- a/build/rpm/pcp.spec.in	2020-05-29 09:16:19.000000000 +1000
++++ b/build/rpm/pcp.spec.in	2020-06-11 13:10:57.402576528 +1000
+@@ -3149,6 +3149,7 @@
+ chown -R pcp:pcp "$PCP_LOG_DIR/pmie" 2>/dev/null
+ chown -R pcp:pcp "$PCP_LOG_DIR/pmproxy" 2>/dev/null
+ %{install_file "$PCP_PMNS_DIR" .NeedRebuild}
++%{install_file "$PCP_LOG_DIR/pmlogger" .NeedRewrite}
+ %if "@enable_systemd@" == "true"
+     %systemd_postun_with_restart pmcd.service
+     %systemd_post pmcd.service
+--- a/debian/pcp.postinst.tail	2019-06-13 09:59:16.000000000 +1000
++++ b/debian/pcp.postinst.tail	2020-06-11 13:10:57.402576528 +1000
+@@ -6,6 +6,8 @@
+ 
+ touch /var/lib/pcp/pmns/.NeedRebuild
+ chmod 644 /var/lib/pcp/pmns/.NeedRebuild
++touch /var/log/pcp/pmlogger/.NeedRewrite
++chmod 644 /var/log/pcp/pmlogger/.NeedRewrite
+ 
+ getent group pcp >/dev/null || groupadd -r pcp
+ getent passwd pcp >/dev/null || \
+--- a/debian/pcp.prerm	2017-08-17 10:54:50.000000000 +1000
++++ b/debian/pcp.prerm	2020-06-11 13:10:57.402576528 +1000
+@@ -24,3 +24,4 @@
+     fi
+ fi
+ rm -f /var/lib/pcp/pmns/.NeedRebuild
++rm -f /var/log/pcp/pmlogger/.NeedRewrite
diff --git a/SOURCES/redhat-bugzilla-1790433.patch b/SOURCES/redhat-bugzilla-1790433.patch
new file mode 100644
index 0000000..31643bd
--- /dev/null
+++ b/SOURCES/redhat-bugzilla-1790433.patch
@@ -0,0 +1,21 @@
+BZ 1790433 - Missing dependency of pcp-pmda-snmp on net-snmp-perl
+bc4abb291 pmdasnmp: add Install checks for the required Net::SNMP module
+
+diff --git a/src/pmdas/snmp/Install b/src/pmdas/snmp/Install
+index 7fe4193e4..21a76ab56 100755
+--- a/src/pmdas/snmp/Install
++++ b/src/pmdas/snmp/Install
+@@ -22,6 +22,13 @@ iam=snmp
+ perl_opt=true
+ daemon_opt=false
+ 
++perl -e "use Net::SNMP" 2>/dev/null
++if test $? -ne 0; then
++    echo "Net::SNMP (Simple Network Management Protocol) perl module is not installed"
++    status=1
++    exit
++fi
++
+ pmdaSetup
+ pmdaInstall
+ exit
diff --git a/SOURCES/redhat-bugzilla-1790452.patch b/SOURCES/redhat-bugzilla-1790452.patch
new file mode 100644
index 0000000..ef28a8f
--- /dev/null
+++ b/SOURCES/redhat-bugzilla-1790452.patch
@@ -0,0 +1,45 @@
+BZ 1790452 - Installation of pcp-pmda-samba causes SELinux issues
+73772a60f selinux: fix pmdasamba(1) operating with selinux enforcing
+
+--- a/qa/917.out.in	2020-05-19 20:34:46.000000000 +1000
++++ pcp-5.1.1/qa/917.out.in	2020-06-22 17:29:14.346713826 +1000
+@@ -34,6 +34,8 @@
+ ! allow [pcp_pmcd_t] [unreserved_port_t] : [tcp_socket] { name_bind name_connect };
+ ! allow [pcp_pmcd_t] [unreserved_port_t] : [udp_socket] { name_bind };
+ ! allow [pcp_pmlogger_t] [unreserved_port_t] : [tcp_socket] { name_bind };
++  allow [pcp_pmcd_t] [samba_var_t] : [dir] { add_name write };
++  allow [pcp_pmcd_t] [samba_var_t] : [file] { create };
+   allow [pcp_pmcd_t] [websm_port_t] : [tcp_socket] { name_connect };
+ ! allow [pcp_pmcd_t] [pcp_tmp_t] : [file] { execute execute_no_trans map };
+   allow [pcp_pmcd_t] [hostname_exec_t] : [file] { execute execute_no_trans getattr open read };
+--- a/src/pmdas/samba/pmdasamba.pl	2020-02-04 14:51:57.000000000 +1100
++++ pcp-5.1.1/src/pmdas/samba/pmdasamba.pl	2020-06-22 17:29:14.346713826 +1000
+@@ -41,6 +41,7 @@
+ 	$pmda->err("pmdasamba failed to open $smbstats pipe: $!");
+ 
+     while (<STATS>) {
++	$_ =~ s/"//g;
+ 	if (m/^\*\*\*\*\s+(\w+[^*]*)\**$/) {
+ 	    my $heading = $1;
+ 	    $heading =~ s/ +$//g;
+--- a/src/selinux/pcpupstream.te.in	2020-05-19 20:34:32.000000000 +1000
++++ pcp-5.1.1/src/selinux/pcpupstream.te.in	2020-06-22 17:29:14.347713837 +1000
+@@ -22,6 +22,7 @@
+ 	type pcp_pmie_exec_t; # pmda.summary
+ 	type ping_exec_t; # pmda.netcheck
+ 	type openvswitch_exec_t; # pmda.openvswitch
++	type samba_var_t; # pmda.samba
+ 	type websm_port_t; # pmda.openmetrics
+         type system_cronjob_t;
+         type user_home_t;
+@@ -151,6 +152,10 @@
+ #type=AVC msg=audit(YYY.94): avc: denied { name_bind } for pid=9365 comm=pmlogger src=4332 scontext=system_u:system_r:pcp_pmlogger_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
+ @PCP_UNRESERVED_PORT_RULE_PMLOGGER@
+ 
++#type=AVC msg=audit(YYY.97): avc: denied { write } for pid=3507787 comm="smbstatus" name="msg.lock" dev="dm-0" ino=283321 scontext=system_u:system_r:pcp_pmcd_t:s0 tcontext=system_u:object_r:samba_var_t:s0 tclass=dir permissive=0
++allow pcp_pmcd_t samba_var_t:dir { add_name write }; # pmda.samba
++allow pcp_pmcd_t samba_var_t:file { create }; # pmda.samba
++
+ #type=AVC msg=audit(YYY.15): avc:  denied  { name_connect } for  pid=13816 comm="python3" dest=9090 scontext=system_u:system_r:pcp_pmcd_t:s0 tcontext=system_u:object_r:websm_port_t:s0 tclass=tcp_socket permissive=0
+ allow pcp_pmcd_t websm_port_t:tcp_socket name_connect; # pmda.openmetrics
+ 
diff --git a/SOURCES/redhat-bugzilla-1792971.patch b/SOURCES/redhat-bugzilla-1792971.patch
new file mode 100644
index 0000000..37e6e69
--- /dev/null
+++ b/SOURCES/redhat-bugzilla-1792971.patch
@@ -0,0 +1,1351 @@
+5af58c8af pmdastatsd: fix minor sizeof issues found by Coverity scan
+b3f78dc82 pmlogconf: fix resource leak found by coverity scan
+8a3ed1b26 pmdastatsd: initialize stack variable to keep Coverity happy
+6902959e5 pmdastatsd: fix Coverity LOCK issues on error paths
+548cad8c5 libpcp_web: ensure context is freed only after timer is fully closed
+01e8bb436 services: pmlogger and pmie services want pmcd on boot
+20959e794 Fix of 1845241 - Intermittent pmlogconf core dumps
+32d6febf4 pcp-atop: resolve other paths of potential null task pointer dereference
+cda567efe pmproxy: improve diagnostics, particularly relating to http requests
+e0bb9e66c pmproxy: cleanup, remove unused flags and dead code in http encoding
+9da331eb8 pmproxy: support the OPTIONS protocol in HTTP 1.1
+1d84081af libpcp_web: add resilience to descriptor lookup paths
+
+--- a/src/pmdas/statsd/src/aggregator-metric-duration-exact.c	2019-08-21 11:33:26.000000000 +1000
++++ b/src/pmdas/statsd/src/aggregator-metric-duration-exact.c	2020-06-11 13:10:57.393576397 +1000
+@@ -45,7 +45,7 @@
+     double** new_values = realloc(collection->values, sizeof(double*) * new_length);
+     ALLOC_CHECK("Unable to allocate memory for collection value.");
+     collection->values = new_values;
+-    collection->values[collection->length] = (double*) malloc(sizeof(double*));
++    collection->values[collection->length] = (double*) malloc(sizeof(double));
+     ALLOC_CHECK("Unable to allocate memory for duration collection value.");
+     *(collection->values[collection->length]) = value;
+     collection->length = new_length;
+--- a/src/pmdas/statsd/src/aggregator-metric-labels.c	2020-02-18 16:32:40.000000000 +1100
++++ b/src/pmdas/statsd/src/aggregator-metric-labels.c	2020-06-11 13:10:57.393576397 +1000
+@@ -140,7 +140,7 @@
+ 
+ static char*
+ create_instance_label_segment_str(char* tags) {
+-    char buffer[JSON_BUFFER_SIZE];
++    char buffer[JSON_BUFFER_SIZE] = {'\0'};
+     size_t tags_length = strlen(tags) + 1;
+     if (tags_length > JSON_BUFFER_SIZE) {
+         return NULL;
+@@ -197,7 +197,7 @@
+     ALLOC_CHECK("Unable to allocate memory for labels string in metric label record.");
+     memcpy((*out)->labels, datagram->tags, labels_length);
+     struct metric_label_metadata* meta = 
+-        (struct metric_label_metadata*) malloc(sizeof(struct metric_label_metadata*));
++        (struct metric_label_metadata*) malloc(sizeof(struct metric_label_metadata));
+     ALLOC_CHECK("Unable to allocate memory for metric label metadata.");
+     (*out)->meta = meta;
+     (*out)->type = METRIC_TYPE_NONE;
+--- a/src/pmdas/statsd/src/network-listener.c	2019-08-27 11:09:16.000000000 +1000
++++ b/src/pmdas/statsd/src/network-listener.c	2020-06-11 13:10:57.393576397 +1000
+@@ -68,7 +68,7 @@
+     struct timeval tv;
+     freeaddrinfo(res);
+     int max_udp_packet_size = config->max_udp_packet_size;
+-    char *buffer = (char *) malloc(max_udp_packet_size * sizeof(char*));
++    char *buffer = (char *) malloc(max_udp_packet_size * sizeof(char));
+     struct sockaddr_storage src_addr;
+     socklen_t src_addr_len = sizeof(src_addr);
+     int rv;
+--- a/src/pmlogconf/pmlogconf.c	2020-05-23 13:33:27.000000000 +1000
++++ b/src/pmlogconf/pmlogconf.c	2020-06-11 13:10:57.394576411 +1000
+@@ -735,7 +735,7 @@
+ static int
+ evaluate_number_values(group_t *group, int type, numeric_cmp_t compare)
+ {
+-    unsigned int	i, found;
++    int			i, found;
+     pmValueSet		*vsp;
+     pmValue		*vp;
+     pmAtomValue		atom;
+@@ -769,7 +769,7 @@
+ static int
+ evaluate_string_values(group_t *group, string_cmp_t compare)
+ {
+-    unsigned int	i, found;
++    int			i, found;
+     pmValueSet		*vsp;
+     pmValue		*vp;
+     pmAtomValue		atom;
+@@ -828,7 +828,7 @@
+ static int
+ evaluate_string_regexp(group_t *group, regex_cmp_t compare)
+ {
+-    unsigned int	i, found;
++    int			i, found;
+     pmValueSet		*vsp;
+     pmValue		*vp;
+     pmAtomValue		atom;
+@@ -1478,6 +1478,10 @@
+ 	} else if (strncmp("#+ groupdir ", bytes, 12) == 0) {
+ 	    group_dircheck(bytes + 12);
+ 	} else if (strncmp("#+ ", bytes, 3) == 0) {
++	    if (group) {
++		/* reported by COVERITY RESOURCE LEAK */
++	    	group_free(group);
++	    }
+ 	    group = group_create(bytes + 3, line);
+ 	    head = 0;
+ 	} else if (group) {
+--- a/src/pmdas/statsd/src/aggregator-metrics.c	2020-02-18 16:32:40.000000000 +1100
++++ b/src/pmdas/statsd/src/aggregator-metrics.c	2020-06-11 13:10:57.394576411 +1000
+@@ -212,7 +212,10 @@
+     VERBOSE_LOG(0, "Writing metrics to file...");
+     pthread_mutex_lock(&container->mutex);
+     metrics* m = container->metrics;
+-    if (strlen(config->debug_output_filename) == 0) return; 
++    if (strlen(config->debug_output_filename) == 0) {
++        pthread_mutex_unlock(&container->mutex);
++        return; 
++    }
+     int sep = pmPathSeparator();
+     char debug_output[MAXPATHLEN];
+     pmsprintf(
+--- a/src/pmdas/statsd/src/aggregator-stats.c	2020-02-18 16:32:40.000000000 +1100
++++ b/src/pmdas/statsd/src/aggregator-stats.c	2020-06-11 13:10:57.394576411 +1000
+@@ -141,7 +141,10 @@
+ write_stats_to_file(struct agent_config* config, struct pmda_stats_container* stats) {
+     VERBOSE_LOG(0, "Writing stats to file...");
+     pthread_mutex_lock(&stats->mutex);
+-    if (strlen(config->debug_output_filename) == 0) return; 
++    if (strlen(config->debug_output_filename) == 0) {
++        pthread_mutex_unlock(&stats->mutex);
++        return; 
++    }
+     int sep = pmPathSeparator();
+     char debug_output[MAXPATHLEN];
+     pmsprintf(
+--- a/src/libpcp_web/src/webgroup.c	2020-05-22 11:29:27.000000000 +1000
++++ b/src/libpcp_web/src/webgroup.c	2020-06-11 13:10:57.394576411 +1000
+@@ -56,17 +56,28 @@
+ }
+ 
+ static void
++webgroup_release_context(uv_handle_t *handle)
++{
++    struct context	*context = (struct context *)handle->data;
++
++    if (pmDebugOptions.http)
++	fprintf(stderr, "releasing context %p\n", context);
++
++    pmwebapi_free_context(context);
++}
++
++static void
+ webgroup_destroy_context(struct context *context, struct webgroups *groups)
+ {
+     context->garbage = 1;
+ 
+     if (pmDebugOptions.http)
+-	fprintf(stderr, "freeing context %p\n", context);
++	fprintf(stderr, "destroying context %p\n", context);
+ 
+     uv_timer_stop(&context->timer);
+     if (groups)
+ 	dictUnlink(groups->contexts, &context->randomid);
+-    pmwebapi_free_context(context);
++    uv_close((uv_handle_t *)&context->timer, webgroup_release_context);
+ }
+ 
+ static void
+--- a/src/pmie/pmie.service.in	2020-05-27 13:36:47.000000000 +1000
++++ b/src/pmie/pmie.service.in	2020-06-11 13:10:57.394576411 +1000
+@@ -4,6 +4,7 @@
+ After=network-online.target pmcd.service
+ After=pmie_check.timer pmie_check.path pmie_daily.timer
+ BindsTo=pmie_check.timer pmie_check.path pmie_daily.timer
++Wants=pmcd.service
+ 
+ [Service]
+ Type=notify
+--- a/src/pmlogger/pmlogger.service.in	2020-05-22 16:48:32.000000000 +1000
++++ b/src/pmlogger/pmlogger.service.in	2020-06-11 13:10:57.394576411 +1000
+@@ -4,6 +4,7 @@
+ After=network-online.target pmcd.service
+ After=pmlogger_check.timer pmlogger_check.path pmlogger_daily.timer pmlogger_daily-poll.timer
+ BindsTo=pmlogger_check.timer pmlogger_check.path pmlogger_daily.timer pmlogger_daily-poll.timer
++Wants=pmcd.service
+ 
+ [Service]
+ Type=notify
+--- a/src/pcp/atop/showgeneric.c	2020-03-30 12:13:55.000000000 +1100
++++ b/src/pcp/atop/showgeneric.c	2020-06-11 13:10:57.395576426 +1000
+@@ -2024,6 +2024,9 @@
+ 	*/
+ 	for (numusers=i=0; i < numprocs; i++, curprocs++)
+ 	{
++	        if (*curprocs == NULL)
++		        continue;
++		
+ 		if (procsuppress(*curprocs, &procsel))
+ 			continue;
+ 
+@@ -2069,6 +2072,9 @@
+ 	*/
+ 	for (numprogs=i=0; i < numprocs; i++, curprocs++)
+ 	{
++	        if (*curprocs == NULL)
++		        continue;
++		
+ 		if (procsuppress(*curprocs, &procsel))
+ 			continue;
+ 
+@@ -2112,6 +2118,9 @@
+ 	*/
+ 	for (numconts=i=0; i < numprocs; i++, curprocs++)
+ 	{
++	        if (*curprocs == NULL)
++		        continue;
++		
+ 		if (procsuppress(*curprocs, &procsel))
+ 			continue;
+ 
+--- a/src/libpcp_web/src/exports	2020-05-22 15:38:47.000000000 +1000
++++ b/src/libpcp_web/src/exports	2020-06-11 13:10:57.397576455 +1000
+@@ -189,3 +189,14 @@
+     pmWebGroupDestroy;
+     sdsKeyDictCallBacks;
+ } PCP_WEB_1.12;
++
++PCP_WEB_1.14 {
++  global:
++    dictFetchValue;
++    http_method_str;
++    http_body_is_final;
++    http_parser_version;
++    http_parser_url_init;
++    http_parser_parse_url;
++    http_parser_settings_init;
++} PCP_WEB_1.13;
+--- a/src/pmproxy/src/http.c	2020-03-23 09:47:47.000000000 +1100
++++ b/src/pmproxy/src/http.c	2020-06-11 13:10:57.398576470 +1000
+@@ -21,6 +21,18 @@
+ static int chunked_transfer_size; /* pmproxy.chunksize, pagesize by default */
+ static int smallest_buffer_size = 128;
+ 
++#define MAX_PARAMS_SIZE 4096
++#define MAX_HEADERS_SIZE 128
++
++static sds HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
++	   HEADER_ACCESS_CONTROL_REQUEST_METHOD,
++	   HEADER_ACCESS_CONTROL_ALLOW_METHODS,
++	   HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
++	   HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
++	   HEADER_ACCESS_CONTROL_ALLOWED_HEADERS,
++	   HEADER_CONNECTION, HEADER_CONTENT_LENGTH,
++	   HEADER_ORIGIN, HEADER_WWW_AUTHENTICATE;
++
+ /*
+  * Simple helpers to manage the cumulative addition of JSON
+  * (arrays and/or objects) to a buffer.
+@@ -121,45 +133,9 @@
+ 	return "text/html";
+     if (flags & HTTP_FLAG_TEXT)
+ 	return "text/plain";
+-    if (flags & HTTP_FLAG_JS)
+-	return "text/javascript";
+-    if (flags & HTTP_FLAG_CSS)
+-	return "text/css";
+-    if (flags & HTTP_FLAG_ICO)
+-	return "image/x-icon";
+-    if (flags & HTTP_FLAG_JPG)
+-	return "image/jpeg";
+-    if (flags & HTTP_FLAG_PNG)
+-	return "image/png";
+-    if (flags & HTTP_FLAG_GIF)
+-	return "image/gif";
+     return "application/octet-stream";
+ }
+ 
+-http_flags
+-http_suffix_type(const char *suffix)
+-{
+-    if (strcmp(suffix, "js") == 0)
+-	return HTTP_FLAG_JS;
+-    if (strcmp(suffix, "ico") == 0)
+-	return HTTP_FLAG_ICO;
+-    if (strcmp(suffix, "css") == 0)
+-	return HTTP_FLAG_CSS;
+-    if (strcmp(suffix, "png") == 0)
+-	return HTTP_FLAG_PNG;
+-    if (strcmp(suffix, "gif") == 0)
+-	return HTTP_FLAG_GIF;
+-    if (strcmp(suffix, "jpg") == 0)
+-	return HTTP_FLAG_JPG;
+-    if (strcmp(suffix, "jpeg") == 0)
+-	return HTTP_FLAG_JPG;
+-    if (strcmp(suffix, "html") == 0)
+-	return HTTP_FLAG_HTML;
+-    if (strcmp(suffix, "txt") == 0)
+-	return HTTP_FLAG_TEXT;
+-    return 0;
+-}
+-
+ static const char * const
+ http_content_encoding(http_flags flags)
+ {
+@@ -259,26 +235,28 @@
+ 
+     header = sdscatfmt(sdsempty(),
+ 		"HTTP/%u.%u %u %s\r\n"
+-		"Connection: Keep-Alive\r\n"
+-		"Access-Control-Allow-Origin: *\r\n"
+-		"Access-Control-Allow-Headers: Accept, Accept-Language, Content-Language, Content-Type\r\n",
++		"%S: Keep-Alive\r\n",
+ 		parser->http_major, parser->http_minor,
+-		sts, http_status_mapping(sts));
++		sts, http_status_mapping(sts), HEADER_CONNECTION);
++    header = sdscatfmt(header,
++		"%S: *\r\n"
++		"%S: %S\r\n",
++		HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
++		HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
++		HEADER_ACCESS_CONTROL_ALLOWED_HEADERS);
+ 
+     if (sts == HTTP_STATUS_UNAUTHORIZED && client->u.http.realm)
+-	header = sdscatfmt(header, "WWW-Authenticate: Basic realm=\"%S\"\r\n",
+-				client->u.http.realm);
++	header = sdscatfmt(header, "%S: Basic realm=\"%S\"\r\n",
++				HEADER_WWW_AUTHENTICATE, client->u.http.realm);
+ 
+-    if ((flags & HTTP_FLAG_STREAMING))
+-	header = sdscatfmt(header, "Transfer-encoding: %s\r\n", "chunked");
+-
+-    if (!(flags & HTTP_FLAG_STREAMING))
+-	header = sdscatfmt(header, "Content-Length: %u\r\n", length);
++    if ((flags & (HTTP_FLAG_STREAMING | HTTP_FLAG_NO_BODY)))
++	header = sdscatfmt(header, "Transfer-encoding: chunked\r\n");
++    else
++	header = sdscatfmt(header, "%S: %u\r\n", HEADER_CONTENT_LENGTH, length);
+ 
+-    header = sdscatfmt(header,
+-		"Content-Type: %s%s\r\n"
+-		"Date: %s\r\n\r\n",
+-		http_content_type(flags), http_content_encoding(flags),
++    header = sdscatfmt(header, "Content-Type: %s%s\r\n",
++		http_content_type(flags), http_content_encoding(flags));
++    header = sdscatfmt(header, "Date: %s\r\n\r\n",
+ 		http_date_string(time(NULL), date, sizeof(date)));
+ 
+     if (pmDebugOptions.http && pmDebugOptions.desperate) {
+@@ -288,8 +266,130 @@
+     return header;
+ }
+ 
++static sds
++http_header_value(struct client *client, sds header)
++{
++    if (client->u.http.headers == NULL)
++	return NULL;
++    return (sds)dictFetchValue(client->u.http.headers, header);
++}
++
++static sds
++http_headers_allowed(sds headers)
++{
++    (void)headers;
++    return sdsdup(HEADER_ACCESS_CONTROL_ALLOWED_HEADERS);
++}
++
++/* check whether the (preflight) method being proposed is acceptable */
++static int
++http_method_allowed(sds value, http_options options)
++{
++    if (strcmp(value, "GET") == 0 && (options & HTTP_OPT_GET))
++	return 1;
++    if (strcmp(value, "PUT") == 0 && (options & HTTP_OPT_PUT))
++	return 1;
++    if (strcmp(value, "POST") == 0 && (options & HTTP_OPT_POST))
++	return 1;
++    if (strcmp(value, "HEAD") == 0 && (options & HTTP_OPT_HEAD))
++	return 1;
++    if (strcmp(value, "TRACE") == 0 && (options & HTTP_OPT_TRACE))
++	return 1;
++    return 0;
++}
++
++static char *
++http_methods_string(char *buffer, size_t length, http_options options)
++{
++    char		*p = buffer;
++
++    /* ensure room for all options, spaces and comma separation */
++    if (!options || length < 48)
++	return NULL;
++
++    memset(buffer, 0, length);
++    if (options & HTTP_OPT_GET)
++	strcat(p, ", GET");
++    if (options & HTTP_OPT_PUT)
++	strcat(p, ", PUT");
++    if (options & HTTP_OPT_HEAD)
++	strcat(p, ", HEAD");
++    if (options & HTTP_OPT_POST)
++	strcat(p, ", POST");
++    if (options & HTTP_OPT_TRACE)
++	strcat(p, ", TRACE");
++    if (options & HTTP_OPT_OPTIONS)
++	strcat(p, ", OPTIONS");
++    return p + 2; /* skip leading comma+space */
++}
++
++static sds
++http_response_trace(struct client *client)
++{
++    dictIterator	*iterator;
++    dictEntry		*entry;
++    sds			result = sdsempty();
++
++    iterator = dictGetSafeIterator(client->u.http.headers);
++    while ((entry = dictNext(iterator)) != NULL)
++	result = sdscatfmt("%S: %S\r\n", dictGetKey(entry), dictGetVal(entry));
++    dictReleaseIterator(iterator);
++    return result;
++}
++
++static sds
++http_response_access(struct client *client, http_code sts, http_options options)
++{
++    struct http_parser	*parser = &client->u.http.parser;
++    char		buffer[64];
++    sds			header, value, result;
++
++    value = http_header_value(client, HEADER_ACCESS_CONTROL_REQUEST_METHOD);
++    if (value && http_method_allowed(value, options) == 0)
++	sts = HTTP_STATUS_METHOD_NOT_ALLOWED;
++
++    parser->http_major = parser->http_minor = 1;
++
++    header = sdscatfmt(sdsempty(),
++		"HTTP/%u.%u %u %s\r\n"
++		"%S: Keep-Alive\r\n",
++		parser->http_major, parser->http_minor,
++		sts, http_status_mapping(sts), HEADER_CONNECTION);
++    header = sdscatfmt(header, "%S: %u\r\n", HEADER_CONTENT_LENGTH, 0);
++
++    if (sts >= HTTP_STATUS_OK && sts < HTTP_STATUS_BAD_REQUEST) {
++	if ((value = http_header_value(client, HEADER_ORIGIN)))
++	    header = sdscatfmt(header, "%S: %S\r\n",
++			        HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, value);
++
++	header = sdscatfmt(header, "%S: %s\r\n",
++			    HEADER_ACCESS_CONTROL_ALLOW_METHODS,
++			    http_methods_string(buffer, sizeof(buffer), options));
++
++	value = http_header_value(client, HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
++	if (value && (result = http_headers_allowed(value)) != NULL) {
++	    header = sdscatfmt(header, "%S: %S\r\n",
++				HEADER_ACCESS_CONTROL_ALLOW_HEADERS, result);
++	    sdsfree(result);
++	}
++    }
++    if (sts == HTTP_STATUS_UNAUTHORIZED && client->u.http.realm)
++	header = sdscatfmt(header, "%S: Basic realm=\"%S\"\r\n",
++			    HEADER_WWW_AUTHENTICATE, client->u.http.realm);
++
++    header = sdscatfmt(header, "Date: %s\r\n\r\n",
++		http_date_string(time(NULL), buffer, sizeof(buffer)));
++
++    if (pmDebugOptions.http && pmDebugOptions.desperate) {
++	fprintf(stderr, "access response to client %p\n", client);
++	fputs(header, stderr);
++    }
++    return header;
++}
++
+ void
+-http_reply(struct client *client, sds message, http_code sts, http_flags type)
++http_reply(struct client *client, sds message,
++		http_code sts, http_flags type, http_options options)
+ {
+     http_flags		flags = client->u.http.flags;
+     char		length[32]; /* hex length */
+@@ -313,6 +413,15 @@
+ 
+ 	suffix = sdsnewlen("0\r\n\r\n", 5);		/* chunked suffix */
+ 	client->u.http.flags &= ~HTTP_FLAG_STREAMING;	/* end of stream! */
++
++    } else if (flags & HTTP_FLAG_NO_BODY) {
++	if (client->u.http.parser.method == HTTP_OPTIONS)
++	    buffer = http_response_access(client, sts, options);
++	else if (client->u.http.parser.method == HTTP_TRACE)
++	    buffer = http_response_trace(client);
++	else	/* HTTP_HEAD */
++	    buffer = http_response_header(client, 0, sts, type);
++	suffix = NULL;
+     } else {	/* regular non-chunked response - headers + response body */
+ 	if (client->buffer == NULL) {
+ 	    suffix = message;
+@@ -326,10 +435,11 @@
+ 	buffer = http_response_header(client, sdslen(suffix), sts, type);
+     }
+ 
+-    if (pmDebugOptions.http) {
+-	fprintf(stderr, "HTTP response (client=%p)\n%s%s",
+-			client, buffer, suffix);
+-    }
++    if (pmDebugOptions.http)
++	fprintf(stderr, "HTTP %s response (client=%p)\n%s%s",
++			http_method_str(client->u.http.parser.method),
++			client, buffer, suffix ? suffix : "");
++
+     client_write(client, buffer, suffix);
+ }
+ 
+@@ -363,7 +473,7 @@
+ 	if (pmDebugOptions.desperate)
+ 	    fputs(message, stderr);
+     }
+-    http_reply(client, message, status, HTTP_FLAG_HTML);
++    http_reply(client, message, status, HTTP_FLAG_HTML, 0);
+ }
+ 
+ void
+@@ -371,6 +481,7 @@
+ {
+     struct http_parser	*parser = &client->u.http.parser;
+     http_flags		flags = client->u.http.flags;
++    const char		*method;
+     sds			buffer, suffix;
+ 
+     /* If the client buffer length is now beyond a set maximum size,
+@@ -390,16 +501,18 @@
+ 		buffer = sdsempty();
+ 	    }
+ 	    /* prepend a chunked transfer encoding message length (hex) */
+-	    buffer = sdscatprintf(buffer, "%lX\r\n", (unsigned long)sdslen(client->buffer));
++	    buffer = sdscatprintf(buffer, "%lX\r\n",
++				 (unsigned long)sdslen(client->buffer));
+ 	    suffix = sdscatfmt(client->buffer, "\r\n");
+ 	    /* reset for next call - original released on I/O completion */
+ 	    client->buffer = NULL;	/* safe, as now held in 'suffix' */
+ 
+ 	    if (pmDebugOptions.http) {
+-		fprintf(stderr, "HTTP chunked buffer (client %p, len=%lu)\n%s"
+-				"HTTP chunked suffix (client %p, len=%lu)\n%s",
+-				client, (unsigned long)sdslen(buffer), buffer,
+-				client, (unsigned long)sdslen(suffix), suffix);
++		method = http_method_str(client->u.http.parser.method);
++		fprintf(stderr, "HTTP %s chunk buffer (client %p, len=%lu)\n%s"
++				"HTTP %s chunk suffix (client %p, len=%lu)\n%s",
++			method, client, (unsigned long)sdslen(buffer), buffer,
++			method, client, (unsigned long)sdslen(suffix), suffix);
+ 	    }
+ 	    client_write(client, buffer, suffix);
+ 
+@@ -527,6 +640,8 @@
+ 
+     if (length == 0)
+ 	return NULL;
++    if (length > MAX_PARAMS_SIZE)
++	return NULL;
+     for (p = url; p < end; p++) {
+ 	if (*p == '\0')
+ 	    break;
+@@ -558,6 +673,11 @@
+     struct servlet	*servlet;
+     sds			url;
+ 
++    if (pmDebugOptions.http || pmDebugOptions.appl0)
++	fprintf(stderr, "HTTP %s %.*s\n",
++			http_method_str(client->u.http.parser.method),
++			(int)length, offset);
++
+     if (!(url = http_url_decode(offset, length, &client->u.http.parameters)))
+ 	return NULL;
+     for (servlet = proxy->servlets; servlet != NULL; servlet = servlet->next) {
+@@ -576,13 +696,24 @@
+ {
+     struct client	*client = (struct client *)request->data;
+     struct servlet	*servlet;
++    sds			buffer;
+     int			sts;
+ 
+     http_client_release(client);	/* new URL, clean slate */
+-
+-    if ((servlet = servlet_lookup(client, offset, length)) != NULL) {
++    /* server options - https://tools.ietf.org/html/rfc7231#section-4.3.7 */
++    if (length == 1 && *offset == '*' &&
++	client->u.http.parser.method == HTTP_OPTIONS) {
++	buffer = http_response_access(client, HTTP_STATUS_OK, HTTP_SERVER_OPTIONS);
++	client_write(client, buffer, NULL);
++    } else if ((servlet = servlet_lookup(client, offset, length)) != NULL) {
+ 	client->u.http.servlet = servlet;
+ 	if ((sts = client->u.http.parser.status_code) == 0) {
++	    if (client->u.http.parser.method == HTTP_OPTIONS ||
++		client->u.http.parser.method == HTTP_TRACE ||
++		client->u.http.parser.method == HTTP_HEAD)
++		client->u.http.flags |= HTTP_FLAG_NO_BODY;
++	    else
++		client->u.http.flags &= ~HTTP_FLAG_NO_BODY;
+ 	    client->u.http.headers = dictCreate(&sdsOwnDictCallBacks, NULL);
+ 	    return 0;
+ 	}
+@@ -616,6 +747,11 @@
+ 
+     if (client->u.http.parser.status_code || !client->u.http.headers)
+ 	return 0;	/* already in process of failing connection */
++    if (dictSize(client->u.http.headers) >= MAX_HEADERS_SIZE) {
++	client->u.http.parser.status_code =
++		HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
++	return 0;
++    }
+ 
+     field = sdsnewlen(offset, length);
+     if (pmDebugOptions.http)
+@@ -826,6 +962,17 @@
+     if (chunked_transfer_size < smallest_buffer_size)
+ 	chunked_transfer_size = smallest_buffer_size;
+ 
++    HEADER_ACCESS_CONTROL_REQUEST_HEADERS = sdsnew("Access-Control-Request-Headers");
++    HEADER_ACCESS_CONTROL_REQUEST_METHOD = sdsnew("Access-Control-Request-Method");
++    HEADER_ACCESS_CONTROL_ALLOW_METHODS = sdsnew("Access-Control-Allow-Methods");
++    HEADER_ACCESS_CONTROL_ALLOW_HEADERS = sdsnew("Access-Control-Allow-Headers");
++    HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = sdsnew("Access-Control-Allow-Origin");
++    HEADER_ACCESS_CONTROL_ALLOWED_HEADERS = sdsnew("Accept, Accept-Language, Content-Language, Content-Type");
++    HEADER_CONNECTION = sdsnew("Connection");
++    HEADER_CONTENT_LENGTH = sdsnew("Content-Length");
++    HEADER_ORIGIN = sdsnew("Origin");
++    HEADER_WWW_AUTHENTICATE = sdsnew("WWW-Authenticate");
++
+     register_servlet(proxy, &pmseries_servlet);
+     register_servlet(proxy, &pmwebapi_servlet);
+ }
+@@ -839,4 +986,15 @@
+ 	servlet->close(proxy);
+ 
+     proxymetrics_close(proxy, METRICS_HTTP);
++
++    sdsfree(HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
++    sdsfree(HEADER_ACCESS_CONTROL_REQUEST_METHOD);
++    sdsfree(HEADER_ACCESS_CONTROL_ALLOW_METHODS);
++    sdsfree(HEADER_ACCESS_CONTROL_ALLOW_HEADERS);
++    sdsfree(HEADER_ACCESS_CONTROL_ALLOW_ORIGIN);
++    sdsfree(HEADER_ACCESS_CONTROL_ALLOWED_HEADERS);
++    sdsfree(HEADER_CONNECTION);
++    sdsfree(HEADER_CONTENT_LENGTH);
++    sdsfree(HEADER_ORIGIN);
++    sdsfree(HEADER_WWW_AUTHENTICATE);
+ }
+--- a/src/pmproxy/src/series.c	2020-02-25 17:47:56.000000000 +1100
++++ b/src/pmproxy/src/series.c	2020-06-11 13:10:57.398576470 +1000
+@@ -1,5 +1,5 @@
+ /*
+- * Copyright (c) 2019 Red Hat.
++ * Copyright (c) 2019-2020 Red Hat.
+  *
+  * This program is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as published
+@@ -15,8 +15,7 @@
+ #include <assert.h>
+ 
+ typedef enum pmSeriesRestKey {
+-    RESTKEY_NONE	= 0,
+-    RESTKEY_SOURCE,
++    RESTKEY_SOURCE	= 1,
+     RESTKEY_DESC,
+     RESTKEY_INSTS,
+     RESTKEY_LABELS,
+@@ -29,7 +28,8 @@
+ 
+ typedef struct pmSeriesRestCommand {
+     const char		*name;
+-    unsigned int	size;
++    unsigned int	namelen : 16;
++    unsigned int	options : 16;
+     pmSeriesRestKey	key;
+ } pmSeriesRestCommand;
+ 
+@@ -39,7 +39,8 @@
+     pmSeriesFlags	flags;
+     pmSeriesTimeWindow	window;
+     uv_work_t		loading;
+-    unsigned int	working;
++    unsigned int	working : 1;
++    unsigned int	options : 16;
+     int			nsids;
+     pmSID		*sids;
+     pmSID		sid;
+@@ -55,16 +56,25 @@
+ } pmSeriesBaton;
+ 
+ static pmSeriesRestCommand commands[] = {
+-    { .key = RESTKEY_QUERY, .name = "query", .size = sizeof("query")-1 },
+-    { .key = RESTKEY_DESC,  .name = "descs",  .size = sizeof("descs")-1 },
+-    { .key = RESTKEY_INSTS, .name = "instances", .size = sizeof("instances")-1 },
+-    { .key = RESTKEY_LABELS, .name = "labels", .size = sizeof("labels")-1 },
+-    { .key = RESTKEY_METRIC, .name = "metrics", .size = sizeof("metrics")-1 },
+-    { .key = RESTKEY_SOURCE, .name = "sources", .size = sizeof("sources")-1 },
+-    { .key = RESTKEY_VALUES, .name = "values", .size = sizeof("values")-1 },
+-    { .key = RESTKEY_LOAD, .name = "load", .size = sizeof("load")-1 },
+-    { .key = RESTKEY_PING, .name = "ping", .size = sizeof("ping")-1 },
+-    { .key = RESTKEY_NONE }
++    { .key = RESTKEY_QUERY, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST,
++	    .name = "query", .namelen = sizeof("query")-1 },
++    { .key = RESTKEY_DESC, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST,
++	    .name = "descs", .namelen = sizeof("descs")-1 },
++    { .key = RESTKEY_INSTS, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST,
++	    .name = "instances", .namelen = sizeof("instances")-1 },
++    { .key = RESTKEY_LABELS, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST,
++	    .name = "labels", .namelen = sizeof("labels")-1 },
++    { .key = RESTKEY_METRIC, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST,
++	    .name = "metrics", .namelen = sizeof("metrics")-1 },
++    { .key = RESTKEY_SOURCE, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST,
++	    .name = "sources", .namelen = sizeof("sources")-1 },
++    { .key = RESTKEY_VALUES, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST,
++	    .name = "values", .namelen = sizeof("values")-1 },
++    { .key = RESTKEY_LOAD, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST,
++	    .name = "load", .namelen = sizeof("load")-1 },
++    { .key = RESTKEY_PING, .options = HTTP_OPTIONS_GET,
++	    .name = "ping", .namelen = sizeof("ping")-1 },
++    { .name = NULL }	/* sentinel */
+ };
+ 
+ /* constant string keys (initialized during servlet setup) */
+@@ -78,8 +88,8 @@
+ static const char pmseries_success[] = "{\"success\":true}\r\n";
+ static const char pmseries_failure[] = "{\"success\":false}\r\n";
+ 
+-static pmSeriesRestKey
+-pmseries_lookup_restkey(sds url)
++static pmSeriesRestCommand *
++pmseries_lookup_rest_command(sds url)
+ {
+     pmSeriesRestCommand	*cp;
+     const char		*name;
+@@ -88,11 +98,11 @@
+ 	strncmp(url, "/series/", sizeof("/series/") - 1) == 0) {
+ 	name = (const char *)url + sizeof("/series/") - 1;
+ 	for (cp = &commands[0]; cp->name; cp++) {
+-	    if (strncmp(cp->name, name, cp->size) == 0)
+-		return cp->key;
++	    if (strncmp(cp->name, name, cp->namelen) == 0)
++		return cp;
+ 	}
+     }
+-    return RESTKEY_NONE;
++    return NULL;
+ }
+ 
+ static void
+@@ -518,6 +528,7 @@
+ {
+     pmSeriesBaton	*baton = (pmSeriesBaton *)arg;
+     struct client	*client = baton->client;
++    http_options	options = baton->options;
+     http_flags		flags = client->u.http.flags;
+     http_code		code;
+     sds			msg;
+@@ -545,7 +556,7 @@
+ 	    msg = sdsnewlen(pmseries_failure, sizeof(pmseries_failure) - 1);
+ 	flags |= HTTP_FLAG_JSON;
+     }
+-    http_reply(client, msg, code, flags);
++    http_reply(client, msg, code, flags, options);
+ }
+ 
+ static void
+@@ -555,6 +566,14 @@
+ 	fprintf(stderr, "series module setup (arg=%p)\n", arg);
+ }
+ 
++static void
++pmseries_log(pmLogLevel level, sds message, void *arg)
++{
++    pmSeriesBaton	*baton = (pmSeriesBaton *)arg;
++
++    proxylog(level, message, baton->client->proxy);
++}
++
+ static pmSeriesSettings pmseries_settings = {
+     .callbacks.on_match		= on_pmseries_match,
+     .callbacks.on_desc		= on_pmseries_desc,
+@@ -567,7 +586,7 @@
+     .callbacks.on_label		= on_pmseries_label,
+     .callbacks.on_done		= on_pmseries_done,
+     .module.on_setup		= pmseries_setup,
+-    .module.on_info		= proxylog,
++    .module.on_info		= pmseries_log,
+ };
+ 
+ static void
+@@ -686,7 +705,6 @@
+     case RESTKEY_PING:
+ 	break;
+ 
+-    case RESTKEY_NONE:
+     default:
+ 	client->u.http.parser.status_code = HTTP_STATUS_BAD_REQUEST;
+ 	break;
+@@ -702,15 +720,16 @@
+ pmseries_request_url(struct client *client, sds url, dict *parameters)
+ {
+     pmSeriesBaton	*baton;
+-    pmSeriesRestKey	key;
++    pmSeriesRestCommand	*command;
+ 
+-    if ((key = pmseries_lookup_restkey(url)) == RESTKEY_NONE)
++    if ((command = pmseries_lookup_rest_command(url)) == NULL)
+ 	return 0;
+ 
+     if ((baton = calloc(1, sizeof(*baton))) != NULL) {
+ 	client->u.http.data = baton;
+ 	baton->client = client;
+-	baton->restkey = key;
++	baton->restkey = command->key;
++	baton->options = command->options;
+ 	pmseries_setup_request_parameters(client, baton, parameters);
+     } else {
+ 	client->u.http.parser.status_code = HTTP_STATUS_INTERNAL_SERVER_ERROR;
+@@ -794,10 +813,12 @@
+ 
+     if (baton->query == NULL) {
+ 	message = sdsnewlen(failed, sizeof(failed) - 1);
+-	http_reply(client, message, HTTP_STATUS_BAD_REQUEST, HTTP_FLAG_JSON);
++	http_reply(client, message, HTTP_STATUS_BAD_REQUEST,
++			HTTP_FLAG_JSON, baton->options);
+     } else if (baton->working) {
+ 	message = sdsnewlen(loading, sizeof(loading) - 1);
+-	http_reply(client, message, HTTP_STATUS_CONFLICT, HTTP_FLAG_JSON);
++	http_reply(client, message, HTTP_STATUS_CONFLICT,
++			HTTP_FLAG_JSON, baton->options);
+     } else {
+ 	uv_queue_work(client->proxy->events, &baton->loading,
+ 			pmseries_load_work, pmseries_load_done);
+@@ -810,8 +831,17 @@
+     pmSeriesBaton	*baton = (pmSeriesBaton *)client->u.http.data;
+     int			sts;
+ 
+-    if (client->u.http.parser.status_code)
++    if (client->u.http.parser.status_code) {
++	on_pmseries_done(-EINVAL, baton);
++	return 1;
++    }
++
++    if (client->u.http.parser.method == HTTP_OPTIONS ||
++	client->u.http.parser.method == HTTP_TRACE ||
++	client->u.http.parser.method == HTTP_HEAD) {
++	on_pmseries_done(0, baton);
+ 	return 0;
++    }
+ 
+     switch (baton->restkey) {
+     case RESTKEY_QUERY:
+--- a/src/pmproxy/src/webapi.c	2020-04-17 15:39:17.000000000 +1000
++++ b/src/pmproxy/src/webapi.c	2020-06-11 13:10:57.399576484 +1000
+@@ -1,5 +1,5 @@
+ /*
+- * Copyright (c) 2019 Red Hat.
++ * Copyright (c) 2019-2020 Red Hat.
+  *
+  * This program is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as published
+@@ -18,8 +18,7 @@
+ #include "util.h"
+ 
+ typedef enum pmWebRestKey {
+-    RESTKEY_NONE	= 0,
+-    RESTKEY_CONTEXT,
++    RESTKEY_CONTEXT	= 1,
+     RESTKEY_METRIC,
+     RESTKEY_FETCH,
+     RESTKEY_INDOM,
+@@ -32,7 +31,8 @@
+ 
+ typedef struct pmWebRestCommand {
+     const char		*name;
+-    unsigned int	size;
++    unsigned int	namelen : 16;
++    unsigned int	options : 16;
+     pmWebRestKey	key;
+ } pmWebRestCommand;
+ 
+@@ -47,6 +47,7 @@
+     sds			password;	/* from basic auth header */
+     unsigned int	times : 1;
+     unsigned int	compat : 1;
++    unsigned int	options : 16;
+     unsigned int	numpmids;
+     unsigned int	numvsets;
+     unsigned int	numinsts;
+@@ -56,21 +57,31 @@
+ } pmWebGroupBaton;
+ 
+ static pmWebRestCommand commands[] = {
+-    { .key = RESTKEY_CONTEXT, .name = "context", .size = sizeof("context")-1 },
+-    { .key = RESTKEY_PROFILE, .name = "profile", .size = sizeof("profile")-1 },
+-    { .key = RESTKEY_SCRAPE, .name = "metrics", .size = sizeof("metrics")-1 },
+-    { .key = RESTKEY_METRIC, .name = "metric", .size = sizeof("metric")-1 },
+-    { .key = RESTKEY_DERIVE, .name = "derive", .size = sizeof("derive")-1 },
+-    { .key = RESTKEY_FETCH, .name = "fetch", .size = sizeof("fetch")-1 },
+-    { .key = RESTKEY_INDOM, .name = "indom", .size = sizeof("indom")-1 },
+-    { .key = RESTKEY_STORE, .name = "store", .size = sizeof("store")-1 },
+-    { .key = RESTKEY_CHILD, .name = "children", .size = sizeof("children")-1 },
+-    { .key = RESTKEY_NONE }
++    { .key = RESTKEY_CONTEXT, .options = HTTP_OPTIONS_GET,
++	    .name = "context", .namelen = sizeof("context")-1 },
++    { .key = RESTKEY_PROFILE, .options = HTTP_OPTIONS_GET,
++	    .name = "profile", .namelen = sizeof("profile")-1 },
++    { .key = RESTKEY_SCRAPE, .options = HTTP_OPTIONS_GET,
++	    .name = "metrics", .namelen = sizeof("metrics")-1 },
++    { .key = RESTKEY_METRIC, .options = HTTP_OPTIONS_GET,
++	    .name = "metric", .namelen = sizeof("metric")-1 },
++    { .key = RESTKEY_DERIVE, .options = HTTP_OPTIONS_GET | HTTP_OPTIONS_POST,
++	    .name = "derive", .namelen = sizeof("derive")-1 },
++    { .key = RESTKEY_FETCH, .options = HTTP_OPTIONS_GET,
++	    .name = "fetch", .namelen = sizeof("fetch")-1 },
++    { .key = RESTKEY_INDOM, .options = HTTP_OPTIONS_GET,
++	    .name = "indom", .namelen = sizeof("indom")-1 },
++    { .key = RESTKEY_STORE, .options = HTTP_OPTIONS_GET,
++	    .name = "store", .namelen = sizeof("store")-1 },
++    { .key = RESTKEY_CHILD, .options = HTTP_OPTIONS_GET,
++	    .name = "children", .namelen = sizeof("children")-1 },
++    { .name = NULL }	/* sentinel */
+ };
+ 
+ static pmWebRestCommand openmetrics[] = {
+-    { .key = RESTKEY_SCRAPE, .name = "/metrics", .size = sizeof("/metrics")-1 },
+-    { .key = RESTKEY_NONE }
++    { .key = RESTKEY_SCRAPE, .options = HTTP_OPTIONS_GET,
++	    .name = "/metrics", .namelen = sizeof("/metrics")-1 },
++    { .name = NULL }	/* sentinel */
+ };
+ 
+ static sds PARAM_NAMES, PARAM_NAME, PARAM_PMIDS, PARAM_PMID,
+@@ -78,8 +89,8 @@
+ 	   PARAM_CONTEXT, PARAM_CLIENT;
+ 
+ 
+-static pmWebRestKey
+-pmwebapi_lookup_restkey(sds url, unsigned int *compat, sds *context)
++static pmWebRestCommand *
++pmwebapi_lookup_rest_command(sds url, unsigned int *compat, sds *context)
+ {
+     pmWebRestCommand	*cp;
+     const char		*name, *ctxid = NULL;
+@@ -94,7 +105,7 @@
+ 		name++;
+ 	    } while (isdigit((int)(*name)));
+ 	    if (*name++ != '/')
+-		return RESTKEY_NONE;
++		return NULL;
+ 	    *context = sdsnewlen(ctxid, name - ctxid - 1);
+ 	}
+ 	if (*name == '_') {
+@@ -102,13 +113,13 @@
+ 	    *compat = 1;	/* backward-compatibility mode */
+ 	}
+ 	for (cp = &commands[0]; cp->name; cp++)
+-	    if (strncmp(cp->name, name, cp->size) == 0)
+-		return cp->key;
++	    if (strncmp(cp->name, name, cp->namelen) == 0)
++		return cp;
+     }
+     for (cp = &openmetrics[0]; cp->name; cp++)
+-	if (strncmp(cp->name, url, cp->size) == 0)
+-	    return cp->key;
+-    return RESTKEY_NONE;
++	if (strncmp(cp->name, url, cp->namelen) == 0)
++	    return cp;
++    return NULL;
+ }
+ 
+ static void
+@@ -584,9 +595,10 @@
+ {
+     pmWebGroupBaton	*baton = (pmWebGroupBaton *)arg;
+     struct client	*client = (struct client *)baton->client;
+-    sds			quoted, msg;
++    http_options	options = baton->options;
+     http_flags		flags = client->u.http.flags;
+     http_code		code;
++    sds			quoted, msg;
+ 
+     if (pmDebugOptions.series)
+ 	fprintf(stderr, "%s: client=%p (sts=%d,msg=%s)\n", "on_pmwebapi_done",
+@@ -596,7 +608,9 @@
+ 	code = HTTP_STATUS_OK;
+ 	/* complete current response with JSON suffix if needed */
+ 	if ((msg = baton->suffix) == NULL) {	/* empty OK response */
+-	    if (flags & HTTP_FLAG_JSON) {
++	    if (flags & HTTP_FLAG_NO_BODY) {
++		msg = sdsempty();
++	    } else if (flags & HTTP_FLAG_JSON) {
+ 		msg = sdsnewlen("{", 1);
+ 		if (context)
+ 		    msg = sdscatfmt(msg, "\"context\":%S,", context);
+@@ -628,10 +642,18 @@
+ 	sdsfree(quoted);
+     }
+ 
+-    http_reply(client, msg, code, flags);
++    http_reply(client, msg, code, flags, options);
+     client_put(client);
+ }
+ 
++static void
++on_pmwebapi_info(pmLogLevel level, sds message, void *arg)
++{
++    pmWebGroupBaton	*baton = (pmWebGroupBaton *)arg;
++
++    proxylog(level, message, baton->client->proxy);
++}
++
+ static pmWebGroupSettings pmwebapi_settings = {
+     .callbacks.on_context	= on_pmwebapi_context,
+     .callbacks.on_metric	= on_pmwebapi_metric,
+@@ -645,7 +667,7 @@
+     .callbacks.on_scrape_labels	= on_pmwebapi_scrape_labels,
+     .callbacks.on_check		= on_pmwebapi_check,
+     .callbacks.on_done		= on_pmwebapi_done,
+-    .module.on_info		= proxylog,
++    .module.on_info		= on_pmwebapi_info,
+ };
+ 
+ /*
+@@ -734,7 +756,6 @@
+ 	client->u.http.flags |= HTTP_FLAG_JSON;
+ 	break;
+ 
+-    case RESTKEY_NONE:
+     default:
+ 	client->u.http.parser.status_code = HTTP_STATUS_BAD_REQUEST;
+ 	break;
+@@ -750,11 +771,11 @@
+ pmwebapi_request_url(struct client *client, sds url, dict *parameters)
+ {
+     pmWebGroupBaton	*baton;
+-    pmWebRestKey	key;
++    pmWebRestCommand	*command;
+     unsigned int	compat = 0;
+     sds			context = NULL;
+ 
+-    if ((key = pmwebapi_lookup_restkey(url, &compat, &context)) == RESTKEY_NONE) {
++    if (!(command = pmwebapi_lookup_rest_command(url, &compat, &context))) {
+ 	sdsfree(context);
+ 	return 0;
+     }
+@@ -762,7 +783,8 @@
+     if ((baton = calloc(1, sizeof(*baton))) != NULL) {
+ 	client->u.http.data = baton;
+ 	baton->client = client;
+-	baton->restkey = key;
++	baton->restkey = command->key;
++	baton->options = command->options;
+ 	baton->compat = compat;
+ 	baton->context = context;
+ 	pmwebapi_setup_request_parameters(client, baton, parameters);
+@@ -885,17 +907,27 @@
+     uv_loop_t		*loop = client->proxy->events;
+     uv_work_t		*work;
+ 
+-    /* fail early if something has already gone wrong */
+-    if (client->u.http.parser.status_code != 0)
++    /* take a reference on the client to prevent freeing races on close */
++    client_get(client);
++
++    if (client->u.http.parser.status_code) {
++	on_pmwebapi_done(NULL, -EINVAL, NULL, baton);
+ 	return 1;
++    }
++
++    if (client->u.http.parser.method == HTTP_OPTIONS ||
++	client->u.http.parser.method == HTTP_TRACE ||
++	client->u.http.parser.method == HTTP_HEAD) {
++	on_pmwebapi_done(NULL, 0, NULL, baton);
++	return 0;
++    }
+ 
+-    if ((work = (uv_work_t *)calloc(1, sizeof(uv_work_t))) == NULL)
++    if ((work = (uv_work_t *)calloc(1, sizeof(uv_work_t))) == NULL) {
++	client_put(client);
+ 	return 1;
++    }
+     work->data = baton;
+ 
+-    /* take a reference on the client to prevent freeing races on close */
+-    client_get(client);
+-
+     /* submit command request to worker thread */
+     switch (baton->restkey) {
+     case RESTKEY_CONTEXT:
+@@ -925,11 +957,10 @@
+     case RESTKEY_SCRAPE:
+ 	uv_queue_work(loop, work, pmwebapi_scrape, pmwebapi_work_done);
+ 	break;
+-    case RESTKEY_NONE:
+     default:
++	pmwebapi_work_done(work, -EINVAL);
+ 	client->u.http.parser.status_code = HTTP_STATUS_BAD_REQUEST;
+-	client_put(client);
+-	free(work);
++	on_pmwebapi_done(NULL, -EINVAL, NULL, baton);
+ 	return 1;
+     }
+     return 0;
+--- a/src/pmproxy/src/http.h	2019-12-02 16:43:20.000000000 +1100
++++ b/src/pmproxy/src/http.h	2020-06-11 13:10:57.398576470 +1000
+@@ -1,5 +1,5 @@
+ /*
+- * Copyright (c) 2019 Red Hat.
++ * Copyright (c) 2019-2020 Red Hat.
+  * 
+  * This program is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as published
+@@ -34,29 +34,39 @@
+     HTTP_FLAG_JSON	= (1<<0),
+     HTTP_FLAG_TEXT	= (1<<1),
+     HTTP_FLAG_HTML	= (1<<2),
+-    HTTP_FLAG_JS	= (1<<3),
+-    HTTP_FLAG_CSS	= (1<<4),
+-    HTTP_FLAG_ICO	= (1<<5),
+-    HTTP_FLAG_JPG	= (1<<6),
+-    HTTP_FLAG_PNG	= (1<<7),
+-    HTTP_FLAG_GIF	= (1<<8),
+     HTTP_FLAG_UTF8	= (1<<10),
+     HTTP_FLAG_UTF16	= (1<<11),
++    HTTP_FLAG_NO_BODY	= (1<<13),
+     HTTP_FLAG_COMPRESS	= (1<<14),
+     HTTP_FLAG_STREAMING	= (1<<15),
+     /* maximum 16 for server.h */
+ } http_flags;
+ 
++typedef enum http_options {
++    HTTP_OPT_GET	= (1 << HTTP_GET),
++    HTTP_OPT_PUT	= (1 << HTTP_PUT),
++    HTTP_OPT_HEAD	= (1 << HTTP_HEAD),
++    HTTP_OPT_POST	= (1 << HTTP_POST),
++    HTTP_OPT_TRACE	= (1 << HTTP_TRACE),
++    HTTP_OPT_OPTIONS	= (1 << HTTP_OPTIONS),
++    /* maximum 16 in command opts fields */
++} http_options;
++
++#define HTTP_COMMON_OPTIONS (HTTP_OPT_HEAD | HTTP_OPT_TRACE | HTTP_OPT_OPTIONS)
++#define HTTP_OPTIONS_GET    (HTTP_COMMON_OPTIONS | HTTP_OPT_GET)
++#define HTTP_OPTIONS_PUT    (HTTP_COMMON_OPTIONS | HTTP_OPT_PUT)
++#define HTTP_OPTIONS_POST   (HTTP_COMMON_OPTIONS | HTTP_OPT_POST)
++#define HTTP_SERVER_OPTIONS (HTTP_OPTIONS_GET | HTTP_OPT_PUT | HTTP_OPT_POST)
++
+ typedef unsigned int http_code;
+ 
+ extern void http_transfer(struct client *);
+-extern void http_reply(struct client *, sds, http_code, http_flags);
++extern void http_reply(struct client *, sds, http_code, http_flags, http_options);
+ extern void http_error(struct client *, http_code, const char *);
+ 
+ extern int http_decode(const char *, size_t, sds);
+ extern const char *http_status_mapping(http_code);
+ extern const char *http_content_type(http_flags);
+-extern http_flags http_suffix_type(const char *);
+ 
+ extern sds http_get_buffer(struct client *);
+ extern void http_set_buffer(struct client *, sds, http_flags);
+--- a/qa/1837	1970-01-01 10:00:00.000000000 +1000
++++ b/qa/1837	2020-06-11 13:10:57.396576440 +1000
+@@ -0,0 +1,55 @@
++#!/bin/sh
++# PCP QA Test No. 1837
++# Exercise PMWEBAPI handling server OPTIONS.
++#
++# Copyright (c) 2020 Red Hat.  All Rights Reserved.
++#
++
++seq=`basename $0`
++echo "QA output created by $seq"
++
++# get standard environment, filters and checks
++. ./common.product
++. ./common.filter
++. ./common.check
++
++_check_series
++which curl >/dev/null 2>&1 || _notrun "No curl binary installed"
++curl --request-targets 2>&1 | grep -q 'requires parameter' && \
++	_notrun "Test requires curl --request-targets option"
++
++status=1	# failure is the default!
++$sudo rm -rf $tmp.* $seq.full
++trap "cd $here; _cleanup; exit \$status" 0 1 2 3 15
++
++pmproxy_was_running=false
++[ -f $PCP_RUN_DIR/pmproxy.pid ] && pmproxy_was_running=true
++echo "pmproxy_was_running=$pmproxy_was_running" >>$here/$seq.full
++
++_cleanup()
++{
++    if $pmproxy_was_running
++    then
++	echo "Restart pmproxy ..." >>$here/$seq.full
++	_service pmproxy restart >>$here/$seq.full 2>&1
++	_wait_for_pmproxy
++    else
++	echo "Stopping pmproxy ..." >>$here/$seq.full
++	_service pmproxy stop >>$here/$seq.full 2>&1
++    fi
++    $sudo rm -f $tmp.*
++}
++
++# real QA test starts here
++_service pmproxy restart >/dev/null 2>&1
++
++curl -isS --request-target "*" -X OPTIONS http://localhost:44322 \
++	2>&1 | tee -a $here/$seq.full | _webapi_header_filter
++
++echo >>$here/$seq.full
++echo "=== pmproxy log ===" >>$here/$seq.full
++cat $PCP_LOG_DIR/pmproxy/pmproxy.log >>$here/$seq.full
++
++# success, all done
++status=0
++exit
+--- a/qa/1837.out	1970-01-01 10:00:00.000000000 +1000
++++ b/qa/1837.out	2020-06-11 13:10:57.397576455 +1000
+@@ -0,0 +1,6 @@
++QA output created by 1837
++
++Access-Control-Allow-Methods: GET, PUT, HEAD, POST, TRACE, OPTIONS
++Content-Length: 0
++Date: DATE
++HTTP/1.1 200 OK
+--- a/qa/780	2020-04-14 14:41:41.000000000 +1000
++++ b/qa/780	2020-06-11 13:10:57.397576455 +1000
+@@ -1,8 +1,8 @@
+ #!/bin/sh
+ # PCP QA Test No. 780
+-# Exercise PMWEBAPI Access-Control-Allow-Origin HTTP header.
++# Exercise PMWEBAPI CORS headers.
+ #
+-# Copyright (c) 2014,2019 Red Hat.
++# Copyright (c) 2014,2019-2020 Red Hat.
+ #
+ 
+ seq=`basename $0`
+@@ -16,7 +16,6 @@
+ _check_series
+ which curl >/dev/null 2>&1 || _notrun "No curl binary installed"
+ 
+-signal=$PCP_BINADM_DIR/pmsignal
+ status=1	# failure is the default!
+ $sudo rm -rf $tmp.* $seq.full
+ trap "cd $here; _cleanup; exit \$status" 0 1 2 3 15
+@@ -39,13 +38,21 @@
+     $sudo rm -f $tmp.*
+ }
+ 
+-unset http_proxy
+-unset HTTP_PROXY
+-
+ # real QA test starts here
+ _service pmproxy restart >/dev/null 2>&1
+ 
+-curl -s -S "http://localhost:44323/pmapi/context" -I | _webapi_header_filter
++echo "=== Basic" | tee -a $here/$seq.full
++curl -IsS "http://localhost:44323/pmapi/context" | _webapi_header_filter
++
++echo "=== Preflight" | tee -a $here/$seq.full
++curl -isS -X OPTIONS "http://localhost:44323/series/query?expr=hinv*" | _webapi_header_filter
++
++echo "=== OK Request Method" | tee -a $here/$seq.full
++curl -isS -X OPTIONS -H "Origin: http://example.com" -H "Access-Control-Request-Method: GET" "http://localhost:44323/pmapi/context" | _webapi_header_filter
++
++echo "=== Bad Request Method" | tee -a $here/$seq.full
++curl -isS -X OPTIONS -H "Origin: http://example.com" -H "Access-Control-Request-Method: BAD" "http://localhost:44323/pmapi/context" | _webapi_header_filter
++
+ echo >>$here/$seq.full
+ echo "=== pmproxy log ===" >>$here/$seq.full
+ cat $PCP_LOG_DIR/pmproxy/pmproxy.log >>$here/$seq.full
+--- a/qa/780.out	2020-03-23 09:47:47.000000000 +1100
++++ b/qa/780.out	2020-06-11 13:10:57.397576455 +1000
+@@ -1,8 +1,27 @@
+ QA output created by 780
++=== Basic
+ 
+ Access-Control-Allow-Headers: Accept, Accept-Language, Content-Language, Content-Type
+ Access-Control-Allow-Origin: *
+-Content-Length: SIZE
+ Content-Type: application/json
+ Date: DATE
+ HTTP/1.1 200 OK
++Transfer-encoding: chunked
++=== Preflight
++
++Access-Control-Allow-Methods: GET, HEAD, POST, TRACE, OPTIONS
++Content-Length: 0
++Date: DATE
++HTTP/1.1 200 OK
++=== OK Request Method
++
++Access-Control-Allow-Methods: GET, HEAD, TRACE, OPTIONS
++Access-Control-Allow-Origin: http://example.com
++Content-Length: 0
++Date: DATE
++HTTP/1.1 200 OK
++=== Bad Request Method
++
++Content-Length: 0
++Date: DATE
++HTTP/1.1 405 Method Not Allowed
+--- a/qa/common.check	2020-05-20 10:51:37.000000000 +1000
++++ b/qa/common.check	2020-06-11 13:10:57.397576455 +1000
+@@ -2696,7 +2696,7 @@
+     tee -a $here/$seq.full \
+     | col -b \
+     | sed \
+-	-e 's/^\(Content-Length:\) [0-9][0-9]*/\1 SIZE/g' \
++	-e 's/^\(Content-Length:\) [1-9][0-9]*/\1 SIZE/g' \
+ 	-e 's/^\(Date:\).*/\1 DATE/g' \
+ 	-e 's/\(\"context\":\) [0-9][0-9]*/\1 CTXID/g' \
+ 	-e '/^Connection: Keep-Alive/d' \
+--- a/qa/group	2020-05-28 09:15:22.000000000 +1000
++++ b/qa/group	2020-06-11 13:10:57.397576455 +1000
+@@ -1757,6 +1757,7 @@
+ 1724 pmda.bpftrace local python
+ 1768 pmfind local
+ 1793 pmrep pcp2xxx python local
++1837 pmproxy local
+ 1855 pmda.rabbitmq local
+ 1896 pmlogger logutil pmlc local
+ 4751 libpcp threads valgrind local pcp
+--- a/qa/1211.out	2020-01-20 16:53:42.000000000 +1100
++++ b/qa/1211.out	2020-06-11 13:10:57.399576484 +1000
+@@ -507,9 +507,11 @@
+ Perform simple source-based query ...
+ 
+ Error handling - descriptor for bad series identifier
+-pmseries: [Error] no descriptor for series identifier no.such.identifier
+ 
+ no.such.identifier
++    PMID: PM_ID_NULL
++    Data Type: ???  InDom: unknown 0xffffffff
++    Semantics: unknown  Units: unknown
+ 
+ Error handling - metric name for bad series identifier
+ 
+--- a/src/libpcp_web/src/query.c	2020-01-20 15:43:31.000000000 +1100
++++ b/src/libpcp_web/src/query.c	2020-06-11 13:10:57.399576484 +1000
+@@ -1938,11 +1938,15 @@
+ 	return -EPROTO;
+     }
+ 
+-    /* sanity check - were we given an invalid series identifier? */
++    /* were we given a non-metric series identifier? (e.g. an instance) */
+     if (elements[0]->type == REDIS_REPLY_NIL) {
+-	infofmt(msg, "no descriptor for series identifier %s", series);
+-	batoninfo(baton, PMLOG_ERROR, msg);
+-	return -EINVAL;
++	desc->indom = sdscpylen(desc->indom, "unknown", 7);
++	desc->pmid = sdscpylen(desc->pmid, "PM_ID_NULL", 10);
++	desc->semantics = sdscpylen(desc->semantics, "unknown", 7);
++	desc->source = sdscpylen(desc->source, "unknown", 7);
++	desc->type = sdscpylen(desc->type, "unknown", 7);
++	desc->units = sdscpylen(desc->units, "unknown", 7);
++	return 0;
+     }
+ 
+     if (extract_string(baton, series, elements[0], &desc->indom, "indom") < 0)
diff --git a/SOURCES/redhat-bugzilla-1846705.patch b/SOURCES/redhat-bugzilla-1846705.patch
new file mode 100644
index 0000000..6504334
--- /dev/null
+++ b/SOURCES/redhat-bugzilla-1846705.patch
@@ -0,0 +1,36 @@
+BZ 1846705 - Possible memory leak detected in pcp-atop
+f30aff90b qa: add valgrind suppressions needed for valgrind 3.16
+
+diff --git a/qa/valgrind-suppress-3.16.0 b/qa/valgrind-suppress-3.16.0
+new file mode 100644
+index 000000000..515591747
+--- /dev/null
++++ b/qa/valgrind-suppress-3.16.0
+@@ -0,0 +1,27 @@
++# qa/1080 and qa/490 and qa/386 and qa/459 on Fedora 32
++# at 0x483880B: malloc (vg_replace_malloc.c:299)
++# by 0x4A0D490: tsearch (in /usr/lib64/libc-2.28.so)
++# by 0x4871EA6: __pmFindPDUBuf (pdubuf.c:126)
++# ...
++{
++   tsearch
++   Memcheck:Leak
++   match-leak-kinds: possible
++   fun:malloc
++   fun:tsearch
++   fun:__pmFindPDUBuf
++   ...
++}
++
++# qa/1080 and qa/490 and qa/386 and qa/459 on Fedora 32
++# at 0x483880B: malloc (vg_replace_malloc.c:299)
++# by 0x4871E5F: __pmFindPDUBuf (pdubuf.c:115)
++# ...
++{
++   findpdubuf
++   Memcheck:Leak
++   match-leak-kinds: possible
++   fun:malloc
++   fun:__pmFindPDUBuf
++   ...
++}
diff --git a/SOURCES/redhat-bugzilla-1846711.patch b/SOURCES/redhat-bugzilla-1846711.patch
new file mode 100644
index 0000000..1c57ff2
--- /dev/null
+++ b/SOURCES/redhat-bugzilla-1846711.patch
@@ -0,0 +1,268 @@
+BZ 1846711 - pcp-pmda-openmetrics produces warnings querying grafana in its default configuration
+0b2ef2d79 pmdaopenmetrics: add control.status metrics, de-verbosify the log, QA updates
+63605e3db qa/1102: tweak openmetrics QA to be more deterministic
+649a0c3a2 qa: improve _filter_pmda_remove() in common.filter
+
+commit 0b2ef2d79686d1e44901263093edeb9e1b9b5f77
+Author: Mark Goodwin <mgoodwin@redhat.com>
+Date:   Fri Jun 19 12:18:47 2020 +1000
+
+    pmdaopenmetrics: add control.status metrics, de-verbosify the log, QA updates
+    
+    Resolves: RHBZ#1846711
+    
+    Add openmetrics.control.status (string status per configured URL
+    of the last fetch) and openmetrics.control.status_code, which
+    is the integer response code (e.g. 200 is success) with discrete
+    semantics.
+    
+    In addition, we now only spam the PMDA log and systemd journal
+    when a URL fetch fails if openmetrics.control.debug is non-zero.
+    Users can instead rely on the new status metrics, which can also
+    be used for service availability monitoring. These metrics
+    complement the openmetrics.control.parse_time, fetch_time and
+    calls counters.
+    
+    Includes QA updates and pmdaopenmetrics(1) doc updates.
+
+diff --git a/qa/1321.out b/qa/1321.out
+index cee072cd2..4533bccd8 100644
+--- a/qa/1321.out
++++ b/qa/1321.out
+@@ -13,6 +13,8 @@ openmetrics.control.calls
+ openmetrics.control.debug
+ openmetrics.control.fetch_time
+ openmetrics.control.parse_time
++openmetrics.control.status
++openmetrics.control.status_code
+ openmetrics.source1.metric1
+ 
+ == Created URL file /var/lib/pcp/pmdas/openmetrics/config.d/source2.url
+@@ -22,6 +24,8 @@ openmetrics.control.calls
+ openmetrics.control.debug
+ openmetrics.control.fetch_time
+ openmetrics.control.parse_time
++openmetrics.control.status
++openmetrics.control.status_code
+ openmetrics.source1.metric1
+ openmetrics.source2.metric1
+ openmetrics.source2.metric2
+@@ -33,6 +37,8 @@ openmetrics.control.calls
+ openmetrics.control.debug
+ openmetrics.control.fetch_time
+ openmetrics.control.parse_time
++openmetrics.control.status
++openmetrics.control.status_code
+ openmetrics.source1.metric1
+ openmetrics.source2.metric1
+ openmetrics.source2.metric2
+@@ -47,6 +53,8 @@ openmetrics.control.calls
+ openmetrics.control.debug
+ openmetrics.control.fetch_time
+ openmetrics.control.parse_time
++openmetrics.control.status
++openmetrics.control.status_code
+ openmetrics.source1.metric1
+ openmetrics.source2.metric1
+ openmetrics.source2.metric2
+@@ -63,6 +71,8 @@ openmetrics.control.calls
+ openmetrics.control.debug
+ openmetrics.control.fetch_time
+ openmetrics.control.parse_time
++openmetrics.control.status
++openmetrics.control.status_code
+ openmetrics.source1.metric1
+ openmetrics.source2.metric1
+ openmetrics.source2.metric2
+diff --git a/src/pmdas/openmetrics/pmdaopenmetrics.1 b/src/pmdas/openmetrics/pmdaopenmetrics.1
+index d3c7aa85f..0c92e2a11 100644
+--- a/src/pmdas/openmetrics/pmdaopenmetrics.1
++++ b/src/pmdas/openmetrics/pmdaopenmetrics.1
+@@ -413,10 +413,37 @@ log mandatory on 2 second {
+ The PMDA maintains special control metrics, as described below.
+ Apart from
+ .BR openmetrics.control.debug ,
+-each of these metrics is a counter and has one instance for each configured metric source.
+-The instance domain is adjusted dynamically as new sources are discovered.
++each of these metrics has one instance for each configured metric source.
++All of these metrics have integer values with counter semantics, except
++.BR openmetrics.control.status ,
++which has a string value.
++It is important to note that fetching any of the
++.B openmetrics.control
++metrics will only update the counters and status values if the corresponding URL is actually fetched.
++If the source URL is not fetched, the control metric values do not trigger a refresh and the control
++values reported represent the most recent fetch of each corresponding source.
++.PP
++The instance domain for the
++.B openmetrics.control
++metrics is adjusted dynamically as new sources are discovered.
+ If there are no sources configured, the metric names are still defined
+ but the instance domain will be empty and a fetch will return no values.
++.IP \fBopenmetrics.control.status\fP
++A string representing the status of the last fetch of the corresponding source.
++This will generally be
++.B success
++for an http response code of 200.
++This metric can be used for service availability monitoring - provided, as stated above,
++the corresponding source URL is fetched too.
++.IP \fBopenmetrics.control.status_code\fP
++This metric is similar to
++.B openmetrics.control.status
++except that it is the integer response code of the last fetch.
++A value of
++.B 200
++usually signifies success and any other value failure.
++This metric can also be used for service availability monitoring, with the same caveats as
++.BR openmetrics.control.status .
+ .IP \fBopenmetrics.control.calls\fP
+ total number of times each configured metric source has been fetched (if it's a URL)
+ or executed (if it's a script), since the PMDA started.
+diff --git a/src/pmdas/openmetrics/pmdaopenmetrics.python b/src/pmdas/openmetrics/pmdaopenmetrics.python
+index a5ed22f13..1486ed676 100755
+--- a/src/pmdas/openmetrics/pmdaopenmetrics.python
++++ b/src/pmdas/openmetrics/pmdaopenmetrics.python
+@@ -1,6 +1,6 @@
+ #!/usr/bin/env pmpython
+ #
+-# Copyright (c) 2017-2019 Red Hat.
++# Copyright (c) 2017-2020 Red Hat.
+ # Copyright (c) 2017 Ronak Jain.
+ #
+ # This program is free software; you can redistribute it and/or modify it
+@@ -704,6 +704,7 @@ class Source(object):
+             return
+ 
+         # fetch the document
++        status_code = 0
+         try:
+             if self.is_scripted:
+                 # Execute file, expecting openmetrics metric data on stdout.
+@@ -715,6 +716,7 @@ class Source(object):
+                     self.document = open(self.url[7:], 'r').read()
+                 else:
+                     r = self.requests.get(self.url, headers=self.headers, timeout=timeout)
++                    status_code = r.status_code
+                     r.raise_for_status() # non-200?  ERROR
+                     # NB: the requests package automatically enables http keep-alive and compression
+                     self.document = r.text
+@@ -723,9 +725,13 @@ class Source(object):
+             incr = int(1000 * (time.time() - fetch_time))
+             self.pmda.stats_fetch_time[self.cluster] += incr
+             self.pmda.stats_fetch_time[0] += incr # total for all sources
++            self.pmda.stats_status[self.cluster] = "success"
++            self.pmda.stats_status_code[self.cluster] = status_code
+ 
+         except Exception as e:
+-            self.pmda.err('Warning: cannot fetch URL or execute script %s: %s' % (self.path, e))
++            self.pmda.stats_status[self.cluster] = 'failed to fetch URL or execute script %s: %s' % (self.path, e)
++            self.pmda.stats_status_code[self.cluster] = status_code
++            self.pmda.debug('Warning: cannot fetch URL or execute script %s: %s' % (self.path, e)) if self.pmda.dbg else None
+             return
+ 
+     def refresh2(self, timeout):
+@@ -844,6 +850,20 @@ class OpenMetricsPMDA(PMDA):
+             pmUnits(0, 0, 0, 0, 0, 0)),
+             'debug flag to enable verbose log messages, to enable: pmstore %s.control.debug 1' % self.pmda_name)
+ 
++        # response status string, per-source end-point
++        self.stats_status = {0:"none"} # status string, keyed by cluster number
++        self.add_metric('%s.control.status' % self.pmda_name, pmdaMetric(self.pmid(0, 5),
++            c_api.PM_TYPE_STRING, self.sources_indom, c_api.PM_SEM_INSTANT,
++            pmUnits(0, 0, 0, 0, 0, 0)), # no units
++            'per-end-point source URL response status after the most recent fetch')
++
++        # response status code, per-source end-point
++        self.stats_status_code = {0:0} # status code, keyed by cluster number
++        self.add_metric('%s.control.status_code' % self.pmda_name, pmdaMetric(self.pmid(0, 6),
++            c_api.PM_TYPE_32, self.sources_indom, c_api.PM_SEM_DISCRETE,
++            pmUnits(0, 0, 0, 0, 0, 0)), # no units
++            'per-end-point source URL response status code after the most recent fetch')
++
+         # schedule a refresh
+         self.set_need_refresh()
+ 
+@@ -961,6 +981,8 @@ class OpenMetricsPMDA(PMDA):
+                     self.stats_fetch_calls[cluster] = 0
+                     self.stats_fetch_time[cluster] = 0
+                     self.stats_parse_time[cluster] = 0
++                    self.stats_status[cluster] = "unknown"
++                    self.stats_status_code[cluster] = 0
+ 
+                     save_cluster_table = True
+                     self.log("Found source %s cluster %d" % (name, cluster))
+@@ -996,6 +1018,10 @@ class OpenMetricsPMDA(PMDA):
+                 return [self.stats_parse_time[inst], 1] if inst in self.stats_parse_time else [c_api.PM_ERR_INST, 0]
+             elif item == 4: # $(pmda_name).control.debug
+                 return [self.dbg, 1]
++            elif item == 5: # per-source status string
++                return [self.stats_status[inst], 1] if inst in self.stats_status else [c_api.PM_ERR_INST, 0]
++            elif item == 6: # per-source status code
++                return [self.stats_status_code[inst], 1] if inst in self.stats_status_code else [c_api.PM_ERR_INST, 0]
+             return [c_api.PM_ERR_PMID, 0]
+ 
+         self.assert_source_invariants(cluster=cluster)
+
+commit 63605e3db4b2821df2a6ffb21507af91d97f3a8b
+Author: Mark Goodwin <mgoodwin@redhat.com>
+Date:   Fri Jun 19 10:02:04 2020 +1000
+
+    qa/1102: tweak openmetrics QA to be more deterministic
+    
+    Now that pmdaopenmetrics is Installed by default with the localhost
+    grafana metrics URL configured, after _pmdaopenmetrics_save_config
+    we need to _pmdaopenmetrics_remove before _pmdaopenmetrics_install
+    to make qa/1102 deterministic.
+
+diff --git a/qa/1102 b/qa/1102
+index f573d14f4..98ff61f5e 100755
+--- a/qa/1102
++++ b/qa/1102
+@@ -46,6 +46,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15
+ _stop_auto_restart pmcd
+ 
+ _pmdaopenmetrics_save_config
++_pmdaopenmetrics_remove
+ _pmdaopenmetrics_install
+ 
+ port=`_find_free_port 10000`
+diff --git a/qa/1102.out b/qa/1102.out
+index 5094e4a82..aa74abe44 100644
+--- a/qa/1102.out
++++ b/qa/1102.out
+@@ -1,5 +1,12 @@
+ QA output created by 1102
+ 
++=== remove openmetrics agent ===
++Culling the Performance Metrics Name Space ...
++openmetrics ... done
++Updating the PMCD control file, and notifying PMCD ...
++[...removing files...]
++Check openmetrics metrics have gone away ... OK
++
+ === openmetrics agent installation ===
+ Fetch and desc openmetrics metrics: success
+ 
+
+commit 649a0c3a2745f549b139ce1250e38a1e90308426
+Author: Mark Goodwin <mgoodwin@redhat.com>
+Date:   Fri Jun 19 09:55:58 2020 +1000
+
+    qa: improve _filter_pmda_remove() in common.filter
+    
+    Filter "Job for pmcd.service canceled" in _filter_pmda_remove.
+    Systemd sometimes (uncommonly) prints this if a PMDA is still
+    starting when a QA test ./Removes it.
+
+diff --git a/qa/common.filter b/qa/common.filter
+index a53d4a49d..b327abedc 100644
+--- a/qa/common.filter
++++ b/qa/common.filter
+@@ -760,6 +760,7 @@ _filter_pmda_remove()
+     _filter_pmda_install |
+     sed \
+ 	-e '/Removing files/d' \
++	-e '/Job for pmcd.service canceled/d' \
+ 	-e '/Updating the PMCD control file/c\
+ Updating the PMCD control file, and notifying PMCD ...\
+ [...removing files...]'
diff --git a/SOURCES/redhat-bugzilla-1848995.patch b/SOURCES/redhat-bugzilla-1848995.patch
new file mode 100644
index 0000000..edabc1a
--- /dev/null
+++ b/SOURCES/redhat-bugzilla-1848995.patch
@@ -0,0 +1,17 @@
+BZ 1848995 - Intermittent pminfo crashes (core dumps)
+51bb36a84 libpcp: minor clarification to previous fix to use __pmHashInit
+d0df00ad1 derive_parser.y.in: fix of incomplete initialization of __pmHashCtl struct for F_REGEX node
+
+diff --git a/src/libpcp/src/derive_parser.y.in b/src/libpcp/src/derive_parser.y.in
+index 9ed375e01..6756daa77 100644
+--- a/src/libpcp/src/derive_parser.y.in
++++ b/src/libpcp/src/derive_parser.y.in
+@@ -2598,6 +2598,8 @@ regexpr	: opt_bang L_PATTERN
+ 		      return -1;
+ 		  }
+ 		  np->data.pattern->invert = $1;
++		  np->data.pattern->used = 0;
++		  __pmHashInit(&np->data.pattern->hash);
+ 		  $$ = np;
+ 		}
+ 
diff --git a/SOURCES/redhat-bugzilla-1849511.patch b/SOURCES/redhat-bugzilla-1849511.patch
new file mode 100644
index 0000000..864a91d
--- /dev/null
+++ b/SOURCES/redhat-bugzilla-1849511.patch
@@ -0,0 +1,996 @@
+BZ 1849511 - resolve covscan and other issues from upstream QA 
+f7f1dd546 pmproxy: complete handling of HTTP/1.1 TRACE requests
+cc662872b qa: add pcp-free-tera archive to pcp-testsuite package
+80639d05b pmlogger_check.sh: major overhaul (diags and systemd fixups)
+460b7ac2a src/pmlogger/rc_pmlogger: use --quick to pmlogger_check
+0b3b4d4ee src/pmlogger/pmlogger_check.service.in: add --skip-primary arg to pmlogger_check
+3a68366a8 src/pmlogger/pmlogger.service.in: change ancillary services from After to Before
+5d65a6035 src/pmlogger/pmlogger_daily.sh: similar changes to pmlogger_check.sh
+ace576907 src/pmlogger/pmlogger_check.sh: fix locking snarfoo
+2b2c3db11 src/pmlogger/pmlogger_daily.sh: fix diagnostic spaghetti
+4cc54287f pmproxy: allow URLs up to 8k in length
+
+diff -auNr pcp-5.1.1-004/man/man1/pmlogger_check.1 pcp-5.1.1-005/man/man1/pmlogger_check.1
+--- pcp-5.1.1-004/man/man1/pmlogger_check.1	2020-04-07 13:31:03.000000000 +1000
++++ pcp-5.1.1-005/man/man1/pmlogger_check.1	2020-06-22 20:08:18.454403788 +1000
+@@ -19,7 +19,7 @@
+ \f3pmlogger_daily\f1 \- administration of Performance Co-Pilot archive log files
+ .SH SYNOPSIS
+ .B $PCP_BINADM_DIR/pmlogger_check
+-[\f3\-CNsTV?\f1]
++[\f3\-CNpqsTV?\f1]
+ [\f3\-c\f1 \f2control\f1]
+ [\f3\-l\f1 \f2logfile\f1]
+ .br
+@@ -269,6 +269,20 @@
+ .TP
+ \fB\-p\fR
+ If this option is specified for
++.B pmlogger_check
++then any line from the control files for the
++.I primary
++.B pmlogger
++will be ignored.
++This option is intended for environments where some system daemon,
++like
++.BR systemd (1),
++is responsible for controlling (starting, stopping, restarting, etc.) the
++.I primary
++.BR pmlogger .
++.TP
++\fB\-p\fR
++If this option is specified for
+ .B pmlogger_daily
+ then the status of the daily processing is polled and if the daily
+ .BR pmlogger (1)
+@@ -296,6 +310,12 @@
+ .B pmlogger_daily
+ are mutually exclusive.
+ .TP
++\fB\-q\fR
++If this option is specified for
++.B pmlogger_check
++then the script will ``quickstart'' avoiding any optional processing
++like file compression.
++.TP
+ \fB\-r\fR, \fB\-\-norewrite\fR
+ This command line option acts as an override and prevents all archive
+ rewriting with
+diff -auNr pcp-5.1.1-004/qa/1837 pcp-5.1.1-005/qa/1837
+--- pcp-5.1.1-004/qa/1837	2020-06-22 20:00:17.636331169 +1000
++++ pcp-5.1.1-005/qa/1837	2020-06-22 20:08:18.457403819 +1000
+@@ -1,6 +1,6 @@
+ #!/bin/sh
+ # PCP QA Test No. 1837
+-# Exercise PMWEBAPI handling server OPTIONS.
++# Exercise PMWEBAPI handling server OPTIONS and TRACE.
+ #
+ # Copyright (c) 2020 Red Hat.  All Rights Reserved.
+ #
+@@ -43,7 +43,12 @@
+ # real QA test starts here
+ _service pmproxy restart >/dev/null 2>&1
+ 
+-curl -isS --request-target "*" -X OPTIONS http://localhost:44322 \
++echo; echo "=== OPTIONS"
++curl -isS -X OPTIONS --request-target "*" http://localhost:44322 \
++	2>&1 | tee -a $here/$seq.full | _webapi_header_filter
++
++echo; echo "=== TRACE"
++curl -isS -X TRACE http://localhost:44322 \
+ 	2>&1 | tee -a $here/$seq.full | _webapi_header_filter
+ 
+ echo >>$here/$seq.full
+diff -auNr pcp-5.1.1-004/qa/1837.out pcp-5.1.1-005/qa/1837.out
+--- pcp-5.1.1-004/qa/1837.out	2020-06-22 20:00:17.637331179 +1000
++++ pcp-5.1.1-005/qa/1837.out	2020-06-22 20:08:18.457403819 +1000
+@@ -1,6 +1,17 @@
+ QA output created by 1837
+ 
++=== OPTIONS
++
+ Access-Control-Allow-Methods: GET, PUT, HEAD, POST, TRACE, OPTIONS
+ Content-Length: 0
+ Date: DATE
+ HTTP/1.1 200 OK
++
++=== TRACE
++
++Accept: */*
++Content-Length: 0
++Date: DATE
++HTTP/1.1 200 OK
++Host: localhost:44322
++User-Agent: curl VERSION
+diff -auNr pcp-5.1.1-004/qa/archives/GNUmakefile pcp-5.1.1-005/qa/archives/GNUmakefile
+--- pcp-5.1.1-004/qa/archives/GNUmakefile	2020-03-19 15:15:42.000000000 +1100
++++ pcp-5.1.1-005/qa/archives/GNUmakefile	2020-06-22 20:08:18.461403861 +1000
+@@ -35,6 +35,7 @@
+ 	  pcp-atop.0.xz pcp-atop.meta pcp-atop.index \
+ 	  pcp-atop-boot.0.xz pcp-atop-boot.meta pcp-atop-boot.index \
+ 	  pcp-dstat.0.xz pcp-dstat.meta pcp-dstat.index \
++	  pcp-free-tera.0.xz pcp-free-tera.meta.xz pcp-free-tera.index \
+ 	  pcp-hotatop.0.xz pcp-hotatop.meta pcp-hotatop.index \
+ 	  pcp-zeroconf.0.xz pcp-zeroconf.meta pcp-zeroconf.index \
+ 	  value-test.0.xz value-test.meta value-test.index \
+diff -auNr pcp-5.1.1-004/qa/common.check pcp-5.1.1-005/qa/common.check
+--- pcp-5.1.1-004/qa/common.check	2020-06-22 20:00:17.637331179 +1000
++++ pcp-5.1.1-005/qa/common.check	2020-06-22 20:08:18.459403840 +1000
+@@ -2697,6 +2697,7 @@
+     | col -b \
+     | sed \
+ 	-e 's/^\(Content-Length:\) [1-9][0-9]*/\1 SIZE/g' \
++	-e 's/^\(User-Agent: curl\).*/\1 VERSION/g' \
+ 	-e 's/^\(Date:\).*/\1 DATE/g' \
+ 	-e 's/\(\"context\":\) [0-9][0-9]*/\1 CTXID/g' \
+ 	-e '/^Connection: Keep-Alive/d' \
+diff -auNr pcp-5.1.1-004/src/pmlogger/pmlogger_check.service.in pcp-5.1.1-005/src/pmlogger/pmlogger_check.service.in
+--- pcp-5.1.1-004/src/pmlogger/pmlogger_check.service.in	2020-05-22 16:40:09.000000000 +1000
++++ pcp-5.1.1-005/src/pmlogger/pmlogger_check.service.in	2020-06-22 20:08:18.452403767 +1000
+@@ -6,7 +6,7 @@
+ [Service]
+ Type=oneshot
+ TimeoutStartSec=25m
+-Environment="PMLOGGER_CHECK_PARAMS=-C"
++Environment="PMLOGGER_CHECK_PARAMS=-C --skip-primary"
+ EnvironmentFile=-@PCP_SYSCONFIG_DIR@/pmlogger_timers
+ ExecStart=@PCP_BINADM_DIR@/pmlogger_check $PMLOGGER_CHECK_PARAMS
+ WorkingDirectory=@PCP_VAR_DIR@
+diff -auNr pcp-5.1.1-004/src/pmlogger/pmlogger_check.sh pcp-5.1.1-005/src/pmlogger/pmlogger_check.sh
+--- pcp-5.1.1-004/src/pmlogger/pmlogger_check.sh	2020-05-04 09:52:04.000000000 +1000
++++ pcp-5.1.1-005/src/pmlogger/pmlogger_check.sh	2020-06-22 20:13:04.029416598 +1000
+@@ -36,16 +36,24 @@
+ echo >$tmp/lock
+ prog=`basename $0`
+ PROGLOG=$PCP_LOG_DIR/pmlogger/$prog.log
++MYPROGLOG=$PROGLOG.$$
+ USE_SYSLOG=true
+ 
+ _cleanup()
+ {
++    if [ -s "$MYPROGLOG" ]
++    then
++	rm -f "$PROGLOG"
++	mv "$MYPROGLOG" "$PROGLOG"
++    else
++	rm -f "$MYPROGLOG"
++    fi
+     $USE_SYSLOG && [ $status -ne 0 ] && \
+     $PCP_SYSLOG_PROG -p daemon.error "$prog failed - see $PROGLOG"
+-    [ -s "$PROGLOG" ] || rm -f "$PROGLOG"
+     lockfile=`cat $tmp/lock 2>/dev/null`
+     rm -f "$lockfile"
+     rm -rf $tmp
++    $VERY_VERBOSE && echo "End: `date '+%F %T.%N'`"
+ }
+ trap "_cleanup; exit \$status" 0 1 2 3 15
+ 
+@@ -86,6 +94,8 @@
+ CHECK_RUNLEVEL=false
+ START_PMLOGGER=true
+ STOP_PMLOGGER=false
++QUICKSTART=false
++SKIP_PRIMARY=false
+ 
+ echo > $tmp/usage
+ cat >> $tmp/usage << EOF
+@@ -94,6 +104,8 @@
+   -l=FILE,--logfile=FILE  send important diagnostic messages to FILE
+   -C                      query system service runlevel information
+   -N,--showme             perform a dry run, showing what would be done
++  -p,--skip-primary       do not start or stop the primary pmlogger instance
++  -q,--quick              quick start, no compression
+   -s,--stop               stop pmlogger processes instead of starting them
+   -T,--terse              produce a terser form of output
+   -V,--verbose            increase diagnostic verbosity
+@@ -117,6 +129,7 @@
+ 	-C)	CHECK_RUNLEVEL=true
+ 		;;
+ 	-l)	PROGLOG="$2"
++		MYPROGLOG="$PROGLOG".$$
+ 		USE_SYSLOG=false
+ 		daily_args="${daily_args} -l $2.from.check"
+ 		shift
+@@ -129,6 +142,10 @@
+ 		KILL="echo + kill"
+ 		daily_args="${daily_args} -N"
+ 		;;
++	-p)	SKIP_PRIMARY=true
++		;;
++	-q)	QUICKSTART=true
++		;;
+ 	-s)	START_PMLOGGER=false
+ 		STOP_PMLOGGER=true
+ 		;;
+@@ -162,9 +179,15 @@
+ 
+ _compress_now()
+ {
+-    # If $PCP_COMPRESSAFTER=0 in the control file(s), compress archives now.
+-    # Invoked just before exit when this script has finished successfully.
+-    $PCP_BINADM_DIR/pmlogger_daily -K $daily_args
++    if $QUICKSTART
++    then
++	$VERY_VERBOSE && echo "Skip compression, -q/--quick on command line"
++    else
++	# If $PCP_COMPRESSAFTER=0 in the control file(s), compress archives now.
++	# Invoked just before exit when this script has finished successfully.
++	$VERY_VERBOSE && echo "Doing compression ..."
++	$PCP_BINADM_DIR/pmlogger_daily -K $daily_args
++    fi
+ }
+ 
+ # after argument checking, everything must be logged to ensure no mail is
+@@ -187,26 +210,37 @@
+     #
+     # Exception ($SHOWME, above) is for -N where we want to see the output.
+     #
+-    touch "$PROGLOG"
+-    chown $PCP_USER:$PCP_GROUP "$PROGLOG" >/dev/null 2>&1
+-    exec 1>"$PROGLOG" 2>&1
++    touch "$MYPROGLOG"
++    chown $PCP_USER:$PCP_GROUP "$MYPROGLOG" >/dev/null 2>&1
++    exec 1>"$MYPROGLOG" 2>&1
++fi
++
++if $VERY_VERBOSE
++then
++    echo "Start: `date '+%F %T.%N'`"
++    if `which pstree >/dev/null 2>&1`
++    then
++	echo "Called from:"
++	pstree -spa $$
++	echo "--- end of pstree output ---"
++    fi
+ fi
+ 
+ # if SaveLogs exists in the $PCP_LOG_DIR/pmlogger directory then save
+-# $PROGLOG there as well with a unique name that contains the date and time
++# $MYPROGLOG there as well with a unique name that contains the date and time
+ # when we're run
+ #
+ if [ -d $PCP_LOG_DIR/pmlogger/SaveLogs ]
+ then
+-    now="`date '+%Y%m%d.%H.%M'`"
+-    link=`echo $PROGLOG | sed -e "s/$prog/SaveLogs\/$prog.$now/"`
++    now="`date '+%Y%m%d.%H.%M.%S'`"
++    link=`echo $MYPROGLOG | sed -e "s/$prog/SaveLogs\/$prog.$now/"`
+     if [ ! -f "$link" ]
+     then
+ 	if $SHOWME
+ 	then
+-	    echo "+ ln $PROGLOG $link"
++	    echo "+ ln $MYPROGLOG $link"
+ 	else
+-	    ln $PROGLOG $link
++	    ln $MYPROGLOG $link
+ 	fi
+     fi
+ fi
+@@ -273,7 +307,7 @@
+ 
+ _unlock()
+ {
+-    rm -f lock
++    rm -f "$1/lock"
+     echo >$tmp/lock
+ }
+ 
+@@ -395,6 +429,41 @@
+     echo "$pid"
+ }
+ 
++# wait for the local pmcd to get going for a primary pmlogger
++# (borrowed from qa/common.check)
++#
++# wait_for_pmcd [maxdelay]
++#
++_wait_for_pmcd()
++{
++    # 5 seconds default seems like a reasonable max time to get going
++    _can_wait=${1-5}
++    _limit=`expr $_can_wait \* 10`
++    _i=0
++    _dead=true
++    while [ $_i -lt $_limit ]
++    do
++	_sts=`pmprobe pmcd.numclients 2>/dev/null | $PCP_AWK_PROG '{print $2}'`
++	if [ "${_sts:-0}" -gt 0 ]
++	then
++	    # numval really > 0, we're done
++	    #
++	    _dead=false
++	    break
++	fi
++	pmsleep 0.1
++	_i=`expr $_i + 1`
++    done
++    if $_dead
++    then
++	date
++	echo "Arrgghhh ... pmcd at localhost failed to start after $_can_wait seconds"
++	echo "=== failing pmprobes ==="
++	pmprobe pmcd.numclients
++	status=1
++    fi
++}
++
+ _check_archive()
+ {
+     if [ ! -e "$logfile" ]
+@@ -531,7 +600,17 @@
+ 	cd "$here"
+ 	line=`expr $line + 1`
+ 
+-	$VERY_VERBOSE && echo "[$controlfile:$line] host=\"$host\" primary=\"$primary\" socks=\"$socks\" dir=\"$dir\" args=\"$args\""
++
++	if $VERY_VERBOSE 
++	then
++	    case "$host"
++	    in
++	    \#*|'')	# comment or empty
++			;;
++	    *)		echo "[$controlfile:$line] host=\"$host\" primary=\"$primary\" socks=\"$socks\" dir=\"$dir\" args=\"$args\""
++	    		;;
++	    esac
++	fi
+ 
+ 	case "$host"
+ 	in
+@@ -599,6 +678,15 @@
+ 	    continue
+ 	fi
+ 
++	# if -s/--skip-primary on the command line, do not process
++	# a control file line for the primary pmlogger
++	#
++	if $SKIP_PRIMARY && [ $primary = y ]
++	then
++	    $VERY_VERBOSE && echo "Skip, -s/--skip-primary on command line"
++	    continue
++	fi
++
+ 	# substitute LOCALHOSTNAME marker in this config line
+ 	# (differently for directory and pcp -h HOST arguments)
+ 	#
+@@ -610,7 +698,7 @@
+ 	then
+ 	    pflag=''
+ 	    [ $primary = y ] && pflag=' -P'
+-	    echo "Check pmlogger$pflag -h $host ... in $dir ..."
++	    echo "Checking for: pmlogger$pflag -h $host ... in $dir ..."
+ 	fi
+ 
+ 	# check for directory duplicate entries
+@@ -664,19 +752,25 @@
+ 	    delay=200	# tenths of a second
+ 	    while [ $delay -gt 0 ]
+ 	    do
+-		if pmlock -v lock >$tmp/out 2>&1
++		if pmlock -v "$dir/lock" >$tmp/out 2>&1
+ 		then
+-		    echo $dir/lock >$tmp/lock
++		    echo "$dir/lock" >$tmp/lock
++		    if $VERY_VERBOSE
++		    then
++			echo "Acquired lock:"
++			ls -l $dir/lock
++		    fi
+ 		    break
+ 		else
+ 		    [ -f $tmp/stamp ] || touch -t `pmdate -30M %Y%m%d%H%M` $tmp/stamp
+-		    if [ -z "`find lock -newer $tmp/stamp -print 2>/dev/null`" ]
++		    find $tmp/stamp -newer "$dir/lock" -print 2>/dev/null >$tmp/tmp
++		    if [ -s $tmp/tmp ]
+ 		    then
+-			if [ -f lock ]
++			if [ -f "$dir/lock" ]
+ 			then
+ 			    echo "$prog: Warning: removing lock file older than 30 minutes"
+ 			    LC_TIME=POSIX ls -l $dir/lock
+-			    rm -f lock
++			    rm -f "$dir/lock"
+ 			else
+ 			    # there is a small timing window here where pmlock
+ 			    # might fail, but the lock file has been removed by
+@@ -714,7 +808,7 @@
+ 			continue
+ 		    fi
+ 		fi
+-		if [ -f lock ]
++		if [ -f "$dir/lock" ]
+ 		then
+ 		    echo "$prog: Warning: is another PCP cron job running concurrently?"
+ 		    LC_TIME=POSIX ls -l $dir/lock
+@@ -753,6 +847,14 @@
+ 		    $VERY_VERBOSE && echo "primary pmlogger process $pid not running"
+ 		    pid=''
+ 		fi
++	    else
++		if $VERY_VERBOSE
++		then
++		    echo "$PCP_TMP_DIR/pmlogger/primary: missing?"
++		    echo "Contents of $PCP_TMP_DIR/pmlogger"
++		    ls -l $PCP_TMP_DIR/pmlogger
++		    echo "--- end of ls output ---"
++		fi
+ 	    fi
+ 	else
+ 	    for log in $PCP_TMP_DIR/pmlogger/[0-9]*
+@@ -798,6 +900,17 @@
+ 		#
+ 		PM_LOG_PORT_DIR="$PCP_TMP_DIR/pmlogger"
+ 		rm -f "$PM_LOG_PORT_DIR/primary"
++		# We really starting the primary pmlogger to work, especially
++		# in the systemd world, so make sure pmcd is ready to accept
++		# connections.
++		#
++		_wait_for_pmcd
++		if [ "$status" = 1 ]
++		then
++		    $VERY_VERBOSE && echo "pmcd not running, skip primary pmlogger"
++		    _unlock "$dir"
++		    continue
++		fi
+ 	    else
+ 		args="-h $host $args"
+ 		envs=""
+@@ -870,7 +983,7 @@
+ 	    then
+ 		echo
+ 		echo "+ ${sock_me}$PMLOGGER $args $LOGNAME"
+-		_unlock
++		_unlock "$dir"
+ 		continue
+ 	    else
+ 		$PCP_BINADM_DIR/pmpost "start pmlogger from $prog for host $host"
+@@ -903,7 +1016,7 @@
+ 	    $PCP_ECHO_PROG $PCP_ECHO_N "$pid ""$PCP_ECHO_C" >> $tmp/pmloggers
+ 	fi
+ 
+-	_unlock
++	_unlock "$dir"
+     done
+ }
+ 
+diff -auNr pcp-5.1.1-004/src/pmlogger/pmlogger_daily.sh pcp-5.1.1-005/src/pmlogger/pmlogger_daily.sh
+--- pcp-5.1.1-004/src/pmlogger/pmlogger_daily.sh	2020-04-07 13:31:03.000000000 +1000
++++ pcp-5.1.1-005/src/pmlogger/pmlogger_daily.sh	2020-06-22 20:08:18.451403756 +1000
+@@ -31,16 +31,24 @@
+ echo >$tmp/lock
+ prog=`basename $0`
+ PROGLOG=$PCP_LOG_DIR/pmlogger/$prog.log
++MYPROGLOG=$PROGLOG.$$
+ USE_SYSLOG=true
+ 
+ _cleanup()
+ {
++    if [ -s "$MYPROGLOG" ]
++    then
++	rm -f "$PROGLOG"
++	mv "$MYPROGLOG" "$PROGLOG"
++    else
++	rm -f "$MYPROGLOG"
++    fi
+     $USE_SYSLOG && [ $status -ne 0 ] && \
+     $PCP_SYSLOG_PROG -p daemon.error "$prog failed - see $PROGLOG"
+-    [ -s "$PROGLOG" ] || rm -f "$PROGLOG"
+     lockfile=`cat $tmp/lock 2>/dev/null`
+     rm -f "$lockfile" "$PCP_RUN_DIR/pmlogger_daily.pid"
+     rm -rf $tmp
++    $VERY_VERBOSE && echo "End: `date '+%F %T.%N'`"
+ }
+ trap "_cleanup; exit \$status" 0 1 2 3 15
+ 
+@@ -215,8 +223,10 @@
+ 		fi
+ 		COMPRESSONLY=true
+ 		PROGLOG=$PCP_LOG_DIR/pmlogger/$prog-K.log
++		MYPROGLOG=$PROGLOG.$$
+ 		;;
+ 	-l)	PROGLOG="$2"
++		MYPROGLOG=$PROGLOG.$$
+ 		USE_SYSLOG=false
+ 		shift
+ 		;;
+@@ -278,6 +288,7 @@
+ 		# $PCP_LOG_DIR/pmlogger/daily.<date>.trace
+ 		#
+ 		PROGLOG=$PCP_LOG_DIR/pmlogger/daily.`date "+%Y%m%d.%H.%M"`.trace
++		MYPROGLOG=$PROGLOG.$$
+ 		VERBOSE=true
+ 		VERY_VERBOSE=true
+ 		MYARGS="$MYARGS -V -V"
+@@ -418,13 +429,23 @@
+     #
+     # Exception ($SHOWME, above) is for -N where we want to see the output.
+     #
+-    touch "$PROGLOG"
+-    chown $PCP_USER:$PCP_GROUP "$PROGLOG" >/dev/null 2>&1
+-    exec 1>"$PROGLOG" 2>&1
++    touch "$MYPROGLOG"
++    chown $PCP_USER:$PCP_GROUP "$MYPROGLOG" >/dev/null 2>&1
++    exec 1>"$MYPROGLOG" 2>&1
++fi
++
++if $VERY_VERBOSE
++then
++    echo "Start: `date '+%F %T.%N'`"
++    if `which pstree >/dev/null 2>&1`
++    then
++	echo "Called from:"
++	pstree -spa $$
++    fi
+ fi
+ 
+ # if SaveLogs exists in the $PCP_LOG_DIR/pmlogger directory then save
+-# $PROGLOG there as well with a unique name that contains the date and time
++# $MYPROGLOG there as well with a unique name that contains the date and time
+ # when we're run ... skip if -N (showme)
+ #
+ if $SHOWME
+@@ -433,15 +454,15 @@
+ else
+     if [ -d $PCP_LOG_DIR/pmlogger/SaveLogs ]
+     then
+-	now="`date '+%Y%m%d.%H.%M'`"
+-	link=`echo $PROGLOG | sed -e "s/$prog/SaveLogs\/$prog.$now/"`
++	now="`date '+%Y%m%d.%H.%M.%S'`"
++	link=`echo $MYPROGLOG | sed -e "s/$prog/SaveLogs\/$prog.$now/"`
+ 	if [ ! -f "$link" ]
+ 	then
+ 	    if $SHOWME
+ 	    then
+-		echo "+ ln $PROGLOG $link"
++		echo "+ ln $MYPROGLOG $link"
+ 	    else
+-		ln $PROGLOG $link
++		ln $MYPROGLOG $link
+ 	    fi
+ 	fi
+     fi
+@@ -487,19 +508,20 @@
+ 	delay=200	# tenths of a second
+ 	while [ $delay -gt 0 ]
+ 	do
+-	    if pmlock -v lock >>$tmp/out 2>&1
++	    if pmlock -v "$1/lock" >>$tmp/out 2>&1
+ 	    then
+-		echo $1/lock >$tmp/lock
++		echo "$1/lock" >$tmp/lock
+ 		break
+ 	    else
+ 		[ -f $tmp/stamp ] || touch -t `pmdate -30M %Y%m%d%H%M` $tmp/stamp
+-		if [ ! -z "`find lock -newer $tmp/stamp -print 2>/dev/null`" ]
++		find $tmp/stamp -newer "$1/lock" -print 2>/dev/null >$tmp/tmp
++		if [ -s $tmp/tmp ]
+ 		then
+-		    if [ -f lock ]
++		    if [ -f "$1/lock" ]
+ 		    then
+ 			_warning "removing lock file older than 30 minutes"
+-			LC_TIME=POSIX ls -l $1/lock
+-			rm -f lock
++			LC_TIME=POSIX ls -l "$1/lock"
++			rm -f "$1/lock"
+ 		    else
+ 			# there is a small timing window here where pmlock
+ 			# might fail, but the lock file has been removed by
+@@ -517,10 +539,10 @@
+ 	then
+ 	    # failed to gain mutex lock
+ 	    #
+-	    if [ -f lock ]
++	    if [ -f "$1/lock" ]
+ 	    then
+ 		_warning "is another PCP cron job running concurrently?"
+-		LC_TIME=POSIX ls -l $1/lock
++		LC_TIME=POSIX ls -l "$1/lock"
+ 	    else
+ 		echo "$prog: `cat $tmp/out`"
+ 	    fi
+@@ -534,7 +556,7 @@
+ 
+ _unlock()
+ {
+-    rm -f lock
++    rm -f "$1/lock"
+     echo >$tmp/lock
+ }
+ 
+@@ -703,6 +725,9 @@
+ # if the directory containing the archive matches, then the name
+ # of the file is the pid.
+ #
++# The pid(s) (if any) appear on stdout, so be careful to send any
++# diagnostics to stderr.
++#
+ _get_non_primary_logger_pid()
+ {
+     pid=''
+@@ -713,7 +738,7 @@
+ 	then
+ 	    _host=`sed -n 2p <$log`
+ 	    _arch=`sed -n 3p <$log`
+-	    $PCP_ECHO_PROG $PCP_ECHO_N "... try $log host=$_host arch=$_arch: ""$PCP_ECHO_C"
++	    $PCP_ECHO_PROG >&2 $PCP_ECHO_N "... try $log host=$_host arch=$_arch: ""$PCP_ECHO_C"
+ 	fi
+ 	# throw away stderr in case $log has been removed by now
+ 	match=`sed -e '3s@/[^/]*$@@' $log 2>/dev/null | \
+@@ -721,19 +746,19 @@
+ BEGIN				{ m = 0 }
+ NR == 3 && $0 == "'$dir'"	{ m = 2; next }
+ END				{ print m }'`
+-	$VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "match=$match ""$PCP_ECHO_C"
++	$VERY_VERBOSE && $PCP_ECHO_PROG >&2 $PCP_ECHO_N "match=$match ""$PCP_ECHO_C"
+ 	if [ "$match" = 2 ]
+ 	then
+ 	    pid=`echo $log | sed -e 's,.*/,,'`
+ 	    if _get_pids_by_name pmlogger | grep "^$pid\$" >/dev/null
+ 	    then
+-		$VERY_VERBOSE && echo "pmlogger process $pid identified, OK"
++		$VERY_VERBOSE && echo >&2 "pmlogger process $pid identified, OK"
+ 		break
+ 	    fi
+-	    $VERY_VERBOSE && echo "pmlogger process $pid not running, skip"
++	    $VERY_VERBOSE && echo >&2 "pmlogger process $pid not running, skip"
+ 	    pid=''
+ 	else
+-	    $VERY_VERBOSE && echo "different directory, skip"
++	    $VERY_VERBOSE && echo >&2 "different directory, skip"
+ 	fi
+     done
+     echo "$pid"
+@@ -1028,6 +1053,8 @@
+ 		pid=''
+ 	    fi
+ 	else
++	    # pid(s) on stdout, diagnostics on stderr
++	    #
+ 	    pid=`_get_non_primary_logger_pid`
+ 	    if $VERY_VERBOSE
+ 	    then
+@@ -1458,7 +1485,7 @@
+ 	    fi
+ 	fi
+ 
+-	_unlock
++	_unlock "$dir"
+     done
+ }
+ 
+diff -auNr pcp-5.1.1-004/src/pmlogger/pmlogger.service.in pcp-5.1.1-005/src/pmlogger/pmlogger.service.in
+--- pcp-5.1.1-004/src/pmlogger/pmlogger.service.in	2020-06-22 20:00:17.634331148 +1000
++++ pcp-5.1.1-005/src/pmlogger/pmlogger.service.in	2020-06-22 20:08:18.452403767 +1000
+@@ -2,7 +2,7 @@
+ Description=Performance Metrics Archive Logger
+ Documentation=man:pmlogger(1)
+ After=network-online.target pmcd.service
+-After=pmlogger_check.timer pmlogger_check.path pmlogger_daily.timer pmlogger_daily-poll.timer
++Before=pmlogger_check.timer pmlogger_check.path pmlogger_daily.timer pmlogger_daily-poll.timer
+ BindsTo=pmlogger_check.timer pmlogger_check.path pmlogger_daily.timer pmlogger_daily-poll.timer
+ Wants=pmcd.service
+ 
+diff -auNr pcp-5.1.1-004/src/pmlogger/rc_pmlogger pcp-5.1.1-005/src/pmlogger/rc_pmlogger
+--- pcp-5.1.1-004/src/pmlogger/rc_pmlogger	2020-04-21 10:42:02.000000000 +1000
++++ pcp-5.1.1-005/src/pmlogger/rc_pmlogger	2020-06-22 20:08:18.453403777 +1000
+@@ -96,7 +96,7 @@
+     bgtmp=`mktemp -d $PCP_DIR/var/tmp/pcp.XXXXXXXXX` || exit 1
+     trap "rm -rf $bgtmp; exit \$bgstatus" 0 1 2 3 15
+ 
+-    pmlogger_check $VFLAG >$bgtmp/pmcheck.out 2>$bgtmp/pmcheck
++    pmlogger_check --quick $VFLAG >$bgtmp/pmcheck.out 2>$bgtmp/pmcheck
+     bgstatus=$?
+     if [ -s $bgtmp/pmcheck ]
+     then
+@@ -125,8 +125,6 @@
+ 	    false
+ 	else
+ 	    # Really start the pmlogger instances based on the control file.
+-	    # Done in the background to avoid delaying the init script,
+-	    # failure notification is external (syslog, log files).
+ 	    #
+ 	    $ECHO $PCP_ECHO_N "Starting pmlogger ..." "$PCP_ECHO_C"
+ 
+@@ -234,11 +232,9 @@
+ if [ $VERBOSE_CTL = on ]
+ then				# For a verbose startup and shutdown
+     ECHO=$PCP_ECHO_PROG
+-    REBUILDOPT=''
+     VFLAG='-V'
+ else				# For a quiet startup and shutdown
+     ECHO=:
+-    REBUILDOPT=-s
+     VFLAG=
+ fi
+ 
+diff -auNr pcp-5.1.1-004/src/pmproxy/src/http.c pcp-5.1.1-005/src/pmproxy/src/http.c
+--- pcp-5.1.1-004/src/pmproxy/src/http.c	2020-06-22 20:00:17.635331158 +1000
++++ pcp-5.1.1-005/src/pmproxy/src/http.c	2020-06-22 20:08:18.460403851 +1000
+@@ -324,17 +324,36 @@
+ }
+ 
+ static sds
+-http_response_trace(struct client *client)
++http_response_trace(struct client *client, int sts)
+ {
++    struct http_parser	*parser = &client->u.http.parser;
+     dictIterator	*iterator;
+     dictEntry		*entry;
+-    sds			result = sdsempty();
++    char		buffer[64];
++    sds			header;
++
++    parser->http_major = parser->http_minor = 1;
++
++    header = sdscatfmt(sdsempty(),
++		"HTTP/%u.%u %u %s\r\n"
++		"%S: Keep-Alive\r\n",
++		parser->http_major, parser->http_minor,
++		sts, http_status_mapping(sts), HEADER_CONNECTION);
++    header = sdscatfmt(header, "%S: %u\r\n", HEADER_CONTENT_LENGTH, 0);
+ 
+     iterator = dictGetSafeIterator(client->u.http.headers);
+     while ((entry = dictNext(iterator)) != NULL)
+-	result = sdscatfmt("%S: %S\r\n", dictGetKey(entry), dictGetVal(entry));
++	header = sdscatfmt(header, "%S: %S\r\n", dictGetKey(entry), dictGetVal(entry));
+     dictReleaseIterator(iterator);
+-    return result;
++
++    header = sdscatfmt(header, "Date: %s\r\n\r\n",
++		http_date_string(time(NULL), buffer, sizeof(buffer)));
++
++    if (pmDebugOptions.http && pmDebugOptions.desperate) {
++	fprintf(stderr, "trace response to client %p\n", client);
++	fputs(header, stderr);
++    }
++    return header;
+ }
+ 
+ static sds
+@@ -418,7 +437,7 @@
+ 	if (client->u.http.parser.method == HTTP_OPTIONS)
+ 	    buffer = http_response_access(client, sts, options);
+ 	else if (client->u.http.parser.method == HTTP_TRACE)
+-	    buffer = http_response_trace(client);
++	    buffer = http_response_trace(client, sts);
+ 	else	/* HTTP_HEAD */
+ 	    buffer = http_response_header(client, 0, sts, type);
+ 	suffix = NULL;
+@@ -533,6 +552,8 @@
+     if (servlet && servlet->on_release)
+ 	servlet->on_release(client);
+     client->u.http.privdata = NULL;
++    client->u.http.servlet = NULL;
++    client->u.http.flags = 0;
+ 
+     if (client->u.http.headers) {
+ 	dictRelease(client->u.http.headers);
+@@ -696,29 +717,39 @@
+ {
+     struct client	*client = (struct client *)request->data;
+     struct servlet	*servlet;
+-    sds			buffer;
+     int			sts;
+ 
+     http_client_release(client);	/* new URL, clean slate */
+-    /* server options - https://tools.ietf.org/html/rfc7231#section-4.3.7 */
+-    if (length == 1 && *offset == '*' &&
+-	client->u.http.parser.method == HTTP_OPTIONS) {
+-	buffer = http_response_access(client, HTTP_STATUS_OK, HTTP_SERVER_OPTIONS);
+-	client_write(client, buffer, NULL);
+-    } else if ((servlet = servlet_lookup(client, offset, length)) != NULL) {
++    /* pass to servlets handling each of our internal request endpoints */
++    if ((servlet = servlet_lookup(client, offset, length)) != NULL) {
+ 	client->u.http.servlet = servlet;
+-	if ((sts = client->u.http.parser.status_code) == 0) {
++	if ((sts = client->u.http.parser.status_code) != 0)
++	    http_error(client, sts, "failed to process URL");
++	else {
+ 	    if (client->u.http.parser.method == HTTP_OPTIONS ||
+ 		client->u.http.parser.method == HTTP_TRACE ||
+ 		client->u.http.parser.method == HTTP_HEAD)
+ 		client->u.http.flags |= HTTP_FLAG_NO_BODY;
+-	    else
+-		client->u.http.flags &= ~HTTP_FLAG_NO_BODY;
+ 	    client->u.http.headers = dictCreate(&sdsOwnDictCallBacks, NULL);
+-	    return 0;
+ 	}
+-	http_error(client, sts, "failed to process URL");
+-    } else {
++    }
++    /* server options - https://tools.ietf.org/html/rfc7231#section-4.3.7 */
++    else if (client->u.http.parser.method == HTTP_OPTIONS) {
++	if (length == 1 && *offset == '*') {
++	    client->u.http.flags |= HTTP_FLAG_NO_BODY;
++	    client->u.http.headers = dictCreate(&sdsOwnDictCallBacks, NULL);
++	} else {
++	    sts = client->u.http.parser.status_code = HTTP_STATUS_BAD_REQUEST;
++	    http_error(client, sts, "no handler for OPTIONS");
++	}
++    }
++    /* server trace - https://tools.ietf.org/html/rfc7231#section-4.3.8 */
++    else if (client->u.http.parser.method == HTTP_TRACE) {
++	client->u.http.flags |= HTTP_FLAG_NO_BODY;
++	client->u.http.headers = dictCreate(&sdsOwnDictCallBacks, NULL);
++    }
++    /* nothing available to respond to this request - inform the client */
++    else {
+ 	sts = client->u.http.parser.status_code = HTTP_STATUS_BAD_REQUEST;
+ 	http_error(client, sts, "no handler for URL");
+     }
+@@ -734,7 +765,7 @@
+     if (pmDebugOptions.http && pmDebugOptions.desperate)
+ 	printf("Body: %.*s\n(client=%p)\n", (int)length, offset, client);
+ 
+-    if (servlet->on_body)
++    if (servlet && servlet->on_body)
+ 	return servlet->on_body(client, offset, length);
+     return 0;
+ }
+@@ -828,7 +859,7 @@
+     }
+ 
+     client->u.http.privdata = NULL;
+-    if (servlet->on_headers)
++    if (servlet && servlet->on_headers)
+ 	sts = servlet->on_headers(client, client->u.http.headers);
+ 
+     /* HTTP Basic Auth for all servlets */
+@@ -857,13 +888,31 @@
+ {
+     struct client	*client = (struct client *)request->data;
+     struct servlet	*servlet = client->u.http.servlet;
++    sds			buffer;
++    int			sts;
+ 
+     if (pmDebugOptions.http)
+ 	fprintf(stderr, "HTTP message complete (client=%p)\n", client);
+ 
+-    if (servlet && servlet->on_done)
+-	return servlet->on_done(client);
+-    return 0;
++    if (servlet) {
++	if (servlet->on_done)
++	    return servlet->on_done(client);
++	return 0;
++    }
++
++    sts = HTTP_STATUS_OK;
++    if (client->u.http.parser.method == HTTP_OPTIONS) {
++	buffer = http_response_access(client, sts, HTTP_SERVER_OPTIONS);
++	client_write(client, buffer, NULL);
++	return 0;
++    }
++    if (client->u.http.parser.method == HTTP_TRACE) {
++	buffer = http_response_trace(client, sts);
++	client_write(client, buffer, NULL);
++	return 0;
++    }
++
++    return 1;
+ }
+ 
+ void
+diff -auNr pcp-5.1.1.orig/qa/1608 pcp-5.1.1/qa/1608
+--- pcp-5.1.1.orig/qa/1608	1970-01-01 10:00:00.000000000 +1000
++++ pcp-5.1.1/qa/1608	2020-06-23 12:16:04.005557293 +1000
+@@ -0,0 +1,58 @@
++#!/bin/sh
++# PCP QA Test No. 1608
++# Exercise a long URL handling in pmproxy.
++#
++# Copyright (c) 2020 Red Hat.  All Rights Reserved.
++#
++
++seq=`basename $0`
++echo "QA output created by $seq"
++
++# get standard environment, filters and checks
++. ./common.product
++. ./common.filter
++. ./common.check
++
++_check_series
++which curl >/dev/null 2>&1 || _notrun "No curl binary installed"
++
++status=1	# failure is the default!
++$sudo rm -rf $tmp $tmp.* $seq.full
++trap "_cleanup; exit \$status" 0 1 2 3 15
++
++pmproxy_was_running=false
++[ -f $PCP_RUN_DIR/pmproxy.pid ] && pmproxy_was_running=true
++echo "pmproxy_was_running=$pmproxy_was_running" >>$here/$seq.full
++
++_cleanup()
++{
++    if $pmproxy_was_running
++    then
++        echo "Restart pmproxy ..." >>$here/$seq.full
++        _service pmproxy restart >>$here/$seq.full 2>&1
++        _wait_for_pmproxy
++    else
++        echo "Stopping pmproxy ..." >>$here/$seq.full
++        _service pmproxy stop >>$here/$seq.full 2>&1
++    fi
++    $sudo rm -f $tmp.*
++}
++
++_webapi_failure_filter()
++{
++    _webapi_header_filter | \
++    sed \
++	-e 's/pmproxy.[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*/PMPROXY\/VERSION/g' \
++    #end
++}
++
++# real QA test starts here
++_service pmproxy restart >/dev/null 2>&1
++
++url="http://localhost:44322/pmapi/context"
++aaa=`head -c 10000 < /dev/zero | tr '\0' '\141'`
++curl -isS -X OPTIONS "${url}?${aaa}" | _webapi_failure_filter
++
++# success, all done
++status=0
++exit
+diff -auNr pcp-5.1.1.orig/qa/1608.out pcp-5.1.1/qa/1608.out
+--- pcp-5.1.1.orig/qa/1608.out	1970-01-01 10:00:00.000000000 +1000
++++ pcp-5.1.1/qa/1608.out	2020-06-23 12:16:04.005557293 +1000
+@@ -0,0 +1,16 @@
++QA output created by 1608
++
++</body>
++</html>
++<body>
++<h1>414 URI Too Long</h1>
++<head><title>414 URI Too Long</title></head>
++<html>
++<p><b>unknown servlet</b>: request URL too long</p><hr>
++<p><small><i>PMPROXY/VERSION</i></small></p>
++Access-Control-Allow-Headers: Accept, Accept-Language, Content-Language, Content-Type
++Access-Control-Allow-Origin: *
++Content-Length: SIZE
++Content-Type: text/html
++Date: DATE
++HTTP/1.1 414 URI Too Long
+diff -auNr pcp-5.1.1.orig/qa/group pcp-5.1.1/qa/group
+--- pcp-5.1.1.orig/qa/group	2020-06-23 12:15:21.335094106 +1000
++++ pcp-5.1.1/qa/group	2020-06-23 12:16:54.256102754 +1000
+@@ -1717,6 +1717,7 @@
+ 1600 pmseries pmcd pmproxy pmlogger local
+ 1601 pmseries pmproxy local
+ 1602 pmproxy local
++1608 pmproxy local
+ 1622 selinux local
+ 1623 libpcp_import collectl local
+ 1644 pmda.perfevent local
+diff -auNr pcp-5.1.1.orig/src/pmproxy/src/http.c pcp-5.1.1/src/pmproxy/src/http.c
+--- pcp-5.1.1.orig/src/pmproxy/src/http.c	2020-06-23 12:15:21.364094421 +1000
++++ pcp-5.1.1/src/pmproxy/src/http.c	2020-06-23 12:16:04.008557325 +1000
+@@ -21,7 +21,9 @@
+ static int chunked_transfer_size; /* pmproxy.chunksize, pagesize by default */
+ static int smallest_buffer_size = 128;
+ 
+-#define MAX_PARAMS_SIZE 4096
++/* https://tools.ietf.org/html/rfc7230#section-3.1.1 */
++#define MAX_URL_SIZE	8192
++#define MAX_PARAMS_SIZE 8000
+ #define MAX_HEADERS_SIZE 128
+ 
+ static sds HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+@@ -720,8 +722,13 @@
+     int			sts;
+ 
+     http_client_release(client);	/* new URL, clean slate */
++
++    if (length >= MAX_URL_SIZE) {
++	sts = client->u.http.parser.status_code = HTTP_STATUS_URI_TOO_LONG;
++	http_error(client, sts, "request URL too long");
++    }
+     /* pass to servlets handling each of our internal request endpoints */
+-    if ((servlet = servlet_lookup(client, offset, length)) != NULL) {
++    else if ((servlet = servlet_lookup(client, offset, length)) != NULL) {
+ 	client->u.http.servlet = servlet;
+ 	if ((sts = client->u.http.parser.status_code) != 0)
+ 	    http_error(client, sts, "failed to process URL");
diff --git a/SPECS/pcp.spec b/SPECS/pcp.spec
index f4065bd..297467b 100644
--- a/SPECS/pcp.spec
+++ b/SPECS/pcp.spec
@@ -1,6 +1,6 @@
 Name:    pcp
 Version: 5.1.1
-Release: 1%{?dist}
+Release: 3%{?dist}
 Summary: System-level performance monitoring and performance management
 License: GPLv2+ and LGPLv2+ and CC-BY
 URL:     https://pcp.io
@@ -8,6 +8,15 @@ URL:     https://pcp.io
 %global  bintray https://bintray.com/artifact/download
 Source0: %{bintray}/pcp/source/pcp-%{version}.src.tar.gz
 
+Patch000: redhat-bugzilla-1792971.patch
+Patch001: redhat-bugzilla-1541406.patch
+Patch002: redhat-bugzilla-1846711.patch
+Patch003: redhat-bugzilla-1848995.patch
+Patch004: redhat-bugzilla-1790452.patch
+Patch005: redhat-bugzilla-1846705.patch
+Patch006: redhat-bugzilla-1849511.patch
+Patch007: redhat-bugzilla-1790433.patch
+
 %if 0%{?fedora} >= 26 || 0%{?rhel} > 7
 %global __python2 python2
 %else
@@ -2214,8 +2223,20 @@ updated policy package.
 
 %prep
 %setup -q
+%patch000 -p1
+%patch001 -p1
+%patch002 -p1
+%patch003 -p1
+%patch004 -p1
+%patch005 -p1
+%patch006 -p1
+%patch007 -p1
 
 %build
+# fix up build version
+_build=`echo %{release} | sed -e 's/\..*$//'`
+sed -i "/PACKAGE_BUILD/s/=[0-9]*/=$_build/" VERSION.pcp
+
 %if !%{disable_python2} && 0%{?default_python} != 3
 export PYTHON=python%{?default_python}
 %endif
@@ -2808,12 +2829,14 @@ pmieconf -c enable dmthin
 
 %post
 PCP_PMNS_DIR=%{_pmnsdir}
+PCP_LOG_DIR=%{_logsdir}
 chown -R pcp:pcp %{_logsdir}/pmcd 2>/dev/null
 chown -R pcp:pcp %{_logsdir}/pmlogger 2>/dev/null
 chown -R pcp:pcp %{_logsdir}/sa 2>/dev/null
 chown -R pcp:pcp %{_logsdir}/pmie 2>/dev/null
 chown -R pcp:pcp %{_logsdir}/pmproxy 2>/dev/null
 %{install_file "$PCP_PMNS_DIR" .NeedRebuild}
+%{install_file "$PCP_LOG_DIR/pmlogger" .NeedRewrite}
 %if !%{disable_systemd}
     %systemd_postun_with_restart pmcd.service
     %systemd_post pmcd.service
@@ -3385,6 +3408,27 @@ chown -R pcp:pcp %{_logsdir}/pmproxy 2>/dev/null
 %endif
 
 %changelog
+* Tue Jun 23 2020 Mark Goodwin <mgoodwin@redhat.com> - 5.1.1-3
+- fix for missing runtime deps on perl Net::SNMP (BZ 1790433)
+- resolve covscan and other issues from upstream QA (BZ 1849511)
+- Possible memory leak detected in pcp-atop (BZ 1846705)
+- Installation of pcp-pmda-samba causes SELinux issues (BZ 1790452)
+- fix Intermittent pminfo crashes (BZ 1848995)
+- Silence openmetrics PMDA warnings, add status metrics (BZ 1846711)
+- set PACKAGE_BUILD in VERSION.pcp so pmcd.build metric is correct
+
+* Thu Jun 11 2020 Mark Goodwin <mgoodwin@redhat.com> - 5.1.1-2
+- activate pmlogger_rewrite on upgrades (BZ 1541406)
+- fix Coverity issues in pmdastatsd and pmlogconf (BZ 1792971)
+- libpcp_web: ensure context is freed only after timer is fully closed
+- services: pmlogger and pmie services Want pmcd on boot
+- fix intermittent pmlogconf core dumps (BZ 1845241)
+- pcp-atop: resolve potential null task pointer dereference
+- pmproxy: improve diagnostics, particularly relating to http requests
+- pmproxy: cleanup, remove unused flags and dead code in http encoding
+- pmproxy: support the OPTIONS protocol in HTTP 1.1
+- libpcp_web: add resilience to descriptor lookup paths (BZ 1837153)
+
 * Fri May 29 2020 Mark Goodwin <mgoodwin@redhat.com> - 5.1.1-1
 - Rebuild to pick up changed HdrHistogram_c version (BZ 1831502)
 - pmdakvm: handle kernel lockdown in integrity mode (BZ 1824297)