commit 515d895c599cb159b1f06d23b2027f72be6c5320
Author: Anastasia <a.lubennikova@postgrespro.ru>
Date:   Thu Oct 3 13:50:54 2019 +0300

    pg_upgrade_ACL_check_v2

diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index b4cf6da..1ac3436 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -90,6 +90,7 @@ check_and_dump_old_cluster(bool live_check)
 
 	get_loadable_libraries();
 
+	get_catalog_procedures(&old_cluster);
 
 	/*
 	 * Check for various failure cases
@@ -149,6 +150,16 @@ check_new_cluster(void)
 
 	check_loadable_libraries();
 
+	/*
+	 * When restoring non-default ACL from old cluster we may attempt to apply
+	 * GRANT for functions whose signatures have changed significantly.
+	 *
+	 * Compare function lists of old cluster and new cluster to catch
+	 * the incompatibility early and report it to user with a nice error message
+	 */
+	get_catalog_procedures(&new_cluster);
+	check_catalog_procedures(&old_cluster, &new_cluster);
+
 	if (user_opts.transfer_mode == TRANSFER_MODE_LINK)
 		check_hard_link();
 
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index 03fd155..8a43bf6 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -13,6 +13,7 @@
 
 #include "access/transam.h"
 #include "catalog/pg_language_d.h"
+#include "fe_utils/string_utils.h"
 
 
 /*
@@ -275,3 +276,204 @@ check_loadable_libraries(void)
 	else
 		check_ok();
 }
+
+/*
+ * qsort comparator for procedure signatures
+ */
+static int
+proc_compare_sig(const void *p1, const void *p2)
+{
+	ProcInfo *proc1 = (ProcInfo *) p1;
+	ProcInfo *proc2 = (ProcInfo *) p2;
+
+	return strcmp(proc1->procsig, proc2->procsig);
+}
+
+/*
+ * Fetch the signatures and ACL of cluster's system procedures.
+ */
+void
+get_catalog_procedures(ClusterInfo *cluster)
+{
+	int			dbnum;
+
+	/*
+	 * Fetch all procedure signatures and ACL.
+	 * Each procedure may have different ACL in different database.
+	 */
+	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
+	{
+		DbInfo	   *dbinfo = &cluster->dbarr.dbs[dbnum];
+		PGconn	   *conn = connectToServer(cluster, dbinfo->db_name);
+		PGresult   *res;
+		int			num_procs;
+		int			rowno;
+
+		/*
+		 * Fetch procedure signatures and ACL of functions that have
+		 * some non default ACL.
+		 */
+		if (cluster->major_version >= 110000)
+		{
+			res = executeQueryOrDie(conn,
+						"select proname::text || '('"
+						" || pg_get_function_arguments(pg_proc.oid)::text"
+						" || ')' as funsig,"
+						" array (SELECT unnest(pg_proc.proacl)"
+						" EXCEPT SELECT unnest(pg_init_privs.initprivs))"
+						" from pg_proc join pg_init_privs"
+						" on pg_proc.oid = pg_init_privs.objoid"
+						" where prokind='f' and proacl != initprivs;");
+		}
+		else
+		{
+			res = executeQueryOrDie(conn,
+					"select proname::text || '('"
+					" || pg_get_function_arguments(pg_proc.oid)::text"
+					" || ')' as funsig,"
+					" array (SELECT unnest(pg_proc.proacl)"
+					" EXCEPT SELECT unnest(pg_init_privs.initprivs))"
+					" from pg_proc join pg_init_privs"
+					" on pg_proc.oid = pg_init_privs.objoid"
+					" where proisagg = false and proacl != initprivs;");
+		}
+
+		num_procs = PQntuples(res);
+		dbinfo->proc_arr.nprocs = num_procs;
+		dbinfo->proc_arr.procs = (ProcInfo *) pg_malloc(sizeof(ProcInfo) * num_procs);
+
+		for (rowno = 0; rowno < num_procs; rowno++)
+		{
+			ProcInfo    *curr = &dbinfo->proc_arr.procs[rowno];
+			char	   *procsig = PQgetvalue(res, rowno, 0);
+			char	   *procacl = PQgetvalue(res, rowno, 1);
+
+			curr->procsig = pg_strdup(procsig);
+			curr->procacl = pg_strdup(procacl);
+		}
+
+		qsort((void *) dbinfo->proc_arr.procs, dbinfo->proc_arr.nprocs,
+			  sizeof(ProcInfo), proc_compare_sig);
+
+		PQclear(res);
+		PQfinish(conn);
+	}
+}
+
+/* helper for check_catalog_procedures() */
+static void
+log_incompatible_procedure(FILE **script, char *output_path,
+						   DbInfo *olddbinfo, bool *new_db,
+						   ProcInfo *curr)
+{
+	char	  **aclitems;
+	int			naclitems;
+	int			i;
+
+	if (*script == NULL && (*script = fopen_priv(output_path, "w")) == NULL)
+		pg_fatal("could not open file \"%s\": %s\n", output_path, strerror(errno));
+
+	if (*new_db)
+	{
+		fprintf(*script, _("\\c %s \n"), olddbinfo->db_name);
+		*new_db = false;
+	}
+
+	if (!parsePGArray(curr->procacl, &aclitems, &naclitems))
+	{
+		if (aclitems)
+			free(aclitems);
+		pg_fatal("check_catalog_procedures failed \n");
+	}
+
+	/* generate REVOKE command for each ACL on this function */
+	for (i = 0; i < naclitems; i++)
+	{
+		char *pos = strchr(aclitems[i], '=');
+		*pos = '\0';
+
+		if (pos != aclitems[i])
+			fprintf(*script, _("REVOKE ALL ON FUNCTION %s FROM %s;\n"),
+					curr->procsig, aclitems[i]);
+		else
+			fprintf(*script, _("REVOKE ALL ON FUNCTION %s FROM PUBLIC;\n"),
+					curr->procsig);
+	}
+}
+
+/*
+ * Check API changes in pg_proc between old_cluster and new_cluster.
+ * Report functions that only exist in old_cluster.
+ *
+ * NOTE It's vital to call it after check_databases_are_compatible(),
+ * because we rely on correct database order.
+ */
+void
+check_catalog_procedures(ClusterInfo *old_cluster, ClusterInfo *new_cluster)
+{
+	int			dbnum;
+	FILE	   *script = NULL;
+	bool		found = false;
+	char		output_path[MAXPGPATH];
+	snprintf(output_path, sizeof(output_path), "catalog_procedures.sql");
+
+	prep_status("Checking for system functions API compatibility\n");
+
+	for (dbnum = 0; dbnum < old_cluster->dbarr.ndbs; dbnum++)
+	{
+		DbInfo	   *olddbinfo = &old_cluster->dbarr.dbs[dbnum];
+		DbInfo	   *newdbinfo = &new_cluster->dbarr.dbs[dbnum];
+		int i, j;
+		bool new_db = true;
+
+		if (strcmp(olddbinfo->db_name, newdbinfo->db_name) != 0)
+			pg_log(PG_FATAL, "check_catalog_procedures failed \n");
+
+		i = j = 0;
+		while (i < olddbinfo->proc_arr.nprocs && j < newdbinfo->proc_arr.nprocs)
+		{
+			ProcInfo    *oldcurr = &olddbinfo->proc_arr.procs[i];
+			ProcInfo    *newcurr = &newdbinfo->proc_arr.procs[j];
+			int			result = strcmp(oldcurr->procsig, newcurr->procsig);
+
+			if (result == 0) /* The function exists in both clusters */
+			{
+				i++;
+				j++;
+			}
+			else if (result < 0) /* The function exists only in the old cluster */
+			{
+				log_incompatible_procedure(&script, output_path,
+									olddbinfo, &new_db, oldcurr);
+				found = true;
+				i++;
+			}
+			else if (result > 0)  /* The function exists only in the new cluster */
+				j++;
+		}
+
+		/* handle tail of old_cluster proc list */
+		while (i < olddbinfo->proc_arr.nprocs)
+		{
+			ProcInfo    *oldcurr = &olddbinfo->proc_arr.procs[i];
+			/* The function exists only in the old cluster */
+			log_incompatible_procedure(&script, output_path,
+									olddbinfo, &new_db, oldcurr);
+			found = true;
+
+			i++;
+		}
+	}
+
+	if (found)
+	{
+		fclose(script);
+		pg_log(PG_REPORT, "fatal\n");
+		pg_fatal("Your installation contains non-default ACL on system procedures,\n"
+				 "which API have changed between versions. To perform upgrade, reset ACL to default.\n"
+				 "Script to REVOKE ALL on problem fuctions is in the file:\n"
+				 "    %s\n\n", output_path);
+	}
+	else
+		check_ok();
+}
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 7e5e971..5ed4f8e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -155,6 +155,19 @@ typedef struct
 	int			nrels;
 } RelInfoArr;
 
+typedef struct
+{
+	char	   *procsig;		/* procedure signature */
+	char	   *procacl;		/* ACL */
+} ProcInfo;
+
+typedef struct
+{
+	ProcInfo    *procs;
+	int			nprocs;
+} ProcInfoArr;
+
+
 /*
  * The following structure represents a relation mapping.
  */
@@ -191,6 +204,7 @@ typedef struct
 	char	   *db_ctype;
 	int			db_encoding;
 	RelInfoArr	rel_arr;		/* array of all user relinfos */
+	ProcInfoArr proc_arr;	/* array of system procedures */
 } DbInfo;
 
 typedef struct
@@ -382,6 +396,9 @@ void		check_hard_link(void);
 
 void		get_loadable_libraries(void);
 void		check_loadable_libraries(void);
+void		get_catalog_procedures(ClusterInfo *cluster);
+void		check_catalog_procedures(ClusterInfo *old_cluster,
+									 ClusterInfo *new_cluster);
 
 /* info.c */
 
