On 2019/12/01 23:58, Grigory Smolkin wrote:
On 11/29/19 11:07 AM, Artur Zakirov wrote:
New version of the patch differs from the previous:
- it doesn't generate script to revoke conflicting permissions (but the patch can be fixed easily) - generates file incompatible_objects_for_acl.txt to report which objects changed their signatures - uses pg_shdepend and pg_depend catalogs to get objects with custom privileges
- uses pg_describe_object() to find objects with different signatures

Currently relations, attributes, languages, functions and procedures are scanned. According to the documentation foreign databases, foreign-data wrappers, foreign servers, schemas and tablespaces also support ACLs. But some of them doesn't have entries initialized during initdb, others like schemas and tablespaces didn't change their names. So the patch doesn't scan such objects.

Grigory it would be great if you'll try the patch. I tested it but I could miss something.

I`ve tested the patch on upgrade from 9.5 to master and it works now, thank you.

Great!

But I think that 'incompatible_objects_for_acl.txt' is not a very informative way of reporting to user the source of the problem. There is no mentions of rolenames that holds permissions, that prevents the upgrade, and even if they were, it is still up to user to conjure an script to revoke all those grants, which is not a very user-friendly.

I tried to find some pattern how pg_upgrade decides to log list of problem objects or to generate SQL script file to execute by user. Nowadays only "Checking for large objects" and "Checking for hash indexes" generate SQL script files and log WARNING message.

I think it should generate 'catalog_procedures.sql' script as in previous version with all REVOKE statements, that are required to execute for pg_upgrade to work.

I updated the patch. It generates "revoke_objects.sql" (similar to v3 patch) now and doesn't rely on --check option. It also logs still FATAL message because it seems pg_upgrade should stop here since it fails later if there are objects with changed identities.

The patch doesn't generate "incompatible_objects_for_acl.txt" now because it would duplicate "revoke_objects.sql".

It now uses pg_identify_object() instead of pg_describe_object(), it is easier to get column names with it.

--
Arthur
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index cdd8788b9e..20a3f26289 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -16,6 +16,7 @@

 static void check_new_cluster_is_empty(void);
 static void check_databases_are_compatible(void);
+static void check_for_changed_signatures(void);
 static void check_locale_and_encoding(DbInfo *olddb, DbInfo *newdb);
 static bool equivalent_locale(int category, const char *loca, const char 
*locb);
 static void check_is_install_user(ClusterInfo *cluster);
@@ -142,6 +143,8 @@ check_and_dump_old_cluster(bool live_check)
        if (GET_MAJOR_VERSION(old_cluster.major_version) <= 804)
                new_9_0_populate_pg_largeobject_metadata(&old_cluster, true);

+       get_non_default_acl_infos(&old_cluster);
+
        /*
         * While not a check option, we do this now because this is the only 
time
         * the old server is running.
@@ -161,6 +164,7 @@ check_new_cluster(void)

        check_new_cluster_is_empty();
        check_databases_are_compatible();
+       check_for_changed_signatures();

        check_loadable_libraries();

@@ -443,6 +447,196 @@ check_databases_are_compatible(void)
        }
 }

+/*
+ * Find the location of the last dot, return NULL if not found.
+ */
+static char *
+last_dot_location(const char *identity)
+{
+       const char *p,
+                          *ret = NULL;
+
+       for (p = identity; *p; p++)
+               if (*p == '.')
+                       ret = p;
+       return unconstify(char *, ret);
+}
+
+/*
+ * check_for_changed_signatures()
+ *
+ * Checks that the old cluster doesn't have non-default ACL's for system 
objects
+ * which had different signatures in the new cluster.  Otherwise generates
+ * revoke_objects.sql.
+ */
+static void
+check_for_changed_signatures(void)
+{
+       PGconn     *conn;
+       PGresult   *res;
+       int                     ntups;
+       int                     i_obj_ident;
+       int                     dbnum;
+       bool            need_check = false;
+       FILE       *script = NULL;
+       bool            found_changed = false;
+       char            output_path[MAXPGPATH];
+
+       prep_status("Checking for system objects to grant or revoke 
privileges");
+
+       for (dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++)
+               if (old_cluster.dbarr.dbs[dbnum].non_def_acl_arr.nacls > 0)
+               {
+                       need_check = true;
+                       break;
+               }
+       /*
+        * The old cluster doesn't have system objects with non-default ACL so
+        * quickly exit.
+        */
+       if (!need_check)
+       {
+               check_ok();
+               return;
+       }
+
+       snprintf(output_path, sizeof(output_path), "revoke_objects.sql");
+
+       conn = connectToServer(&new_cluster, "template1");
+       res = executeQueryOrDie(conn,
+               /* Get relations, languages, functions and procedures */
+               "SELECT obj.type, obj.identity "
+               "FROM pg_catalog.pg_depend d, "
+               "LATERAL pg_catalog.pg_identify_object("
+               "    d.refclassid, d.refobjid, d.refobjsubid) obj "
+               "WHERE deptype = 'p' AND refclassid IN ('pg_proc'::regclass, "
+               "                      'pg_class'::regclass, 
'pg_language'::regclass) "
+               "UNION ALL "
+               /* ...and union it with columns */
+               "SELECT obj.type, obj.identity "
+               "FROM pg_catalog.pg_depend d "
+               "INNER JOIN pg_catalog.pg_attribute att ON att.attrelid = 
d.refobjid, "
+               "LATERAL pg_catalog.pg_identify_object("
+               "    d.refclassid, att.attrelid, att.attnum) obj "
+               "WHERE d.deptype = 'p' AND d.refclassid = 'pg_class'::regclass "
+               "ORDER BY 2;");
+       ntups = PQntuples(res);
+
+       i_obj_ident = PQfnumber(res, "identity");
+
+       for (dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++)
+       {
+               DbInfo     *dbinfo = &old_cluster.dbarr.dbs[dbnum];
+               bool            db_used = false;
+               int                     aclnum = 0,
+                                       objnum = 0;
+
+               /*
+                * For every database check system objects with non-default ACL.
+                *
+                * AclInfo array is sorted by obj_ident.  This allows us to 
compare
+                * AclInfo entries with the query result above efficiently.
+                */
+               for (aclnum = 0; aclnum < dbinfo->non_def_acl_arr.nacls; 
aclnum++)
+               {
+                       AclInfo    *aclinfo = 
&dbinfo->non_def_acl_arr.aclinfos[aclnum];
+                       bool            found = false;
+
+                       while (objnum < ntups)
+                       {
+                               int                     ret;
+
+                               ret = strcmp(PQgetvalue(res, objnum, 
i_obj_ident),
+                                                        aclinfo->obj_ident);
+                               /*
+                                * The new cluster doesn't have an object with 
same identity,
+                                * exit the loop, report below and check next 
object.
+                                */
+                               if (ret < 0)
+                                       break;
+                               /*
+                                * The new cluster has an object with same 
identity, just exit
+                                * the loop.
+                                */
+                               else if (ret == 0)
+                               {
+                                       found = true;
+                                       objnum++;
+                                       break;
+                               }
+
+                               objnum++;
+                       }
+
+                       if (!found)
+                       {
+                               found_changed = true;
+                               if (script == NULL && (script = 
fopen_priv(output_path, "w")) == NULL)
+                                       pg_fatal("could not open file \"%s\": 
%s\n",
+                                                        output_path, 
strerror(errno));
+                               if (!db_used)
+                               {
+                                       PQExpBufferData conn_buf;
+
+                                       initPQExpBuffer(&conn_buf);
+                                       appendPsqlMetaConnect(&conn_buf, 
dbinfo->db_name);
+                                       fputs(conn_buf.data, script);
+                                       termPQExpBuffer(&conn_buf);
+
+                                       db_used = true;
+                               }
+
+                               if (strstr(aclinfo->obj_type, "column") == NULL)
+                                       fprintf(script, "REVOKE ALL ON %s %s 
FROM %s;\n",
+                                                       aclinfo->obj_type,
+                                                       /* pg_identify_object() 
quotes identity if necessary */
+                                                       aclinfo->obj_ident,
+                                                       /* role_names is 
already quoted */
+                                                       aclinfo->role_names);
+                               else
+                               {
+                                       char       *pdot = 
last_dot_location(aclinfo->obj_ident);
+                                       PQExpBufferData ident_buf;
+
+                                       if (pdot == NULL || *(pdot + 1) == '\0')
+                                               pg_fatal("invalid column 
identity \"%s\"",
+                                                                
aclinfo->obj_ident);
+
+                                       initPQExpBuffer(&ident_buf);
+                                       appendBinaryPQExpBuffer(&ident_buf, 
aclinfo->obj_ident,
+                                                                               
        pdot - aclinfo->obj_ident);
+
+                                       fprintf(script, "REVOKE ALL (%s) ON %s 
FROM %s;\n",
+                                                       /* pg_identify_object() 
quotes identity if necessary */
+                                                       pdot + 1, 
ident_buf.data,
+                                                       /* role_names is 
already quoted */
+                                                       aclinfo->role_names);
+                                       termPQExpBuffer(&ident_buf);
+                               }
+                       }
+               }
+       }
+
+       PQclear(res);
+       PQfinish(conn);
+
+       if (script)
+               fclose(script);
+
+       if (found_changed)
+       {
+               pg_log(PG_REPORT, "fatal\n");
+               pg_fatal("Your installation contains non-default privileges for 
system objects\n"
+                                "for which the API has changed.  To perform 
the upgrade, reset these\n"
+                                "privileges to default.  The file\n"
+                                "    %s\n"
+                                "when executed by psql will revoke non-default 
privileges for those objects\n\n",
+                                output_path);
+       }
+       else
+               check_ok();
+}
+

 /*
  * create_script_for_cluster_analyze()
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index d67d3a4894..ebac412f55 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -11,6 +11,7 @@

 #include "access/transam.h"
 #include "catalog/pg_class_d.h"
+#include "fe_utils/string_utils.h"
 #include "pg_upgrade.h"

 static void create_rel_filename_map(const char *old_data, const char *new_data,
@@ -23,6 +24,7 @@ static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
 static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
 static void free_rel_infos(RelInfoArr *rel_arr);
+static void free_acl_infos(AclInfoArr *acl_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);

@@ -328,6 +330,96 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 }


+/*
+ * get_non_default_acl_infos()
+ *
+ * Gets AclInfo array of system functions, procedures and columns information
+ * which have non-default ACL.
+ *
+ * Note: the resulting AclInfo array is assumed to be sorted by identity.
+ */
+void
+get_non_default_acl_infos(ClusterInfo *cluster)
+{
+       int                     dbnum;
+
+       for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
+       {
+               DbInfo     *dbinfo = &cluster->dbarr.dbs[dbnum];
+               PGconn     *conn = connectToServer(cluster, dbinfo->db_name);
+               PGresult   *res;
+               AclInfo    *aclinfos;
+               AclInfo    *curr;
+               int                     nacls = 0,
+                                       size_acls = 8;
+               int                     aclnum = 0;
+               int                     i_obj_type,
+                                       i_obj_ident,
+                                       i_rolname;
+
+               res = executeQueryOrDie(conn,
+                       /*
+                        * Get relations, attributes, languages, functions and 
procedures.
+                        */
+                       "SELECT obj.type, obj.identity, shd.refobjid::regrole 
rolename "
+                       "FROM pg_catalog.pg_shdepend shd, "
+                       "LATERAL pg_catalog.pg_identify_object("
+                       "    shd.classid, shd.objid, shd.objsubid) obj "
+                       "WHERE shd.deptype = 'a' AND shd.dbid = %d "
+                       "    AND shd.classid IN ('pg_proc'::regclass, 
'pg_class'::regclass, "
+                       "                        'pg_language'::regclass) "
+                       /* get only system objects */
+                       "    AND obj.schema = 'pg_catalog' "
+                       /*
+                        * Sort only by identity.  It should be enough to 
uniquely compare
+                        * objects later in check_for_changed_signatures().
+                        */
+                       "ORDER BY 2;", dbinfo->db_oid);
+
+               i_obj_type = PQfnumber(res, "type");
+               i_obj_ident = PQfnumber(res, "identity");
+               i_rolname = PQfnumber(res, "rolename");
+
+               aclinfos = (AclInfo *) pg_malloc(sizeof(AclInfo) * size_acls);
+
+               while (aclnum < PQntuples(res))
+               {
+                       PQExpBufferData roles_buf;
+
+                       if (nacls == size_acls)
+                       {
+                               size_acls *= 2;
+                               aclinfos = (AclInfo *) pg_realloc(aclinfos,
+                                                                               
                  sizeof(AclInfo) * size_acls);
+                       }
+                       curr = &aclinfos[nacls++];
+                       curr->obj_type = pg_strdup(PQgetvalue(res, aclnum, 
i_obj_type));
+                       curr->obj_ident = pg_strdup(PQgetvalue(res, aclnum, 
i_obj_ident));
+
+                       initPQExpBuffer(&roles_buf);
+                       /* Group all role names by objects type and identity */
+                       while (aclnum < PQntuples(res) &&
+                                  strcmp(curr->obj_ident, PQgetvalue(res, 
aclnum, i_obj_ident)) == 0)
+                       {
+                               if (roles_buf.len != 0)
+                                       appendPQExpBufferChar(&roles_buf, ',');
+
+                               appendPQExpBufferStr(&roles_buf,
+                                       quote_identifier(PQgetvalue(res, 
aclnum, i_rolname)));
+                               aclnum++;
+                       }
+                       curr->role_names = roles_buf.data;
+               }
+
+               PQclear(res);
+               PQfinish(conn);
+
+               dbinfo->non_def_acl_arr.aclinfos = aclinfos;
+               dbinfo->non_def_acl_arr.nacls = nacls;
+       }
+}
+
+
 /*
  * get_db_infos()
  *
@@ -595,6 +687,7 @@ free_db_and_rel_infos(DbInfoArr *db_arr)
        for (dbnum = 0; dbnum < db_arr->ndbs; dbnum++)
        {
                free_rel_infos(&db_arr->dbs[dbnum].rel_arr);
+               free_acl_infos(&db_arr->dbs[dbnum].non_def_acl_arr);
                pg_free(db_arr->dbs[dbnum].db_name);
        }
        pg_free(db_arr->dbs);
@@ -620,6 +713,21 @@ free_rel_infos(RelInfoArr *rel_arr)
        rel_arr->nrels = 0;
 }

+static void
+free_acl_infos(AclInfoArr *acl_arr)
+{
+       int                     aclnum;
+
+       for (aclnum = 0; aclnum < acl_arr->nacls; aclnum++)
+       {
+               pg_free(acl_arr->aclinfos[aclnum].obj_type);
+               pg_free(acl_arr->aclinfos[aclnum].obj_ident);
+               pg_free(acl_arr->aclinfos[aclnum].role_names);
+       }
+       pg_free(acl_arr->aclinfos);
+       acl_arr->nacls = 0;
+}
+

 static void
 print_db_infos(DbInfoArr *db_arr)
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 729f86aa32..bfc6e20de6 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -149,6 +149,23 @@ typedef struct
        int                     nrels;
 } RelInfoArr;

+/*
+ * Structure to store objects information to check if it changed its signature.
+ */
+typedef struct
+{
+       char       *obj_type;           /* type name */
+       char       *obj_ident;          /* complete object identity */
+       char       *role_names;         /* list of role names which have 
permissions
+                                                                * on the 
object */
+} AclInfo;
+
+typedef struct
+{
+       AclInfo    *aclinfos;
+       int                     nacls;
+} AclInfoArr;
+
 /*
  * The following structure represents a relation mapping.
  */
@@ -185,6 +202,8 @@ typedef struct
        char       *db_ctype;
        int                     db_encoding;
        RelInfoArr      rel_arr;                /* array of all user relinfos */
+       AclInfoArr      non_def_acl_arr;        /* array of objects info with 
non default
+                                                                        * ACL 
*/
 } DbInfo;

 typedef struct
@@ -392,6 +411,7 @@ FileNameMap *gen_db_file_maps(DbInfo *old_db,
                                                          DbInfo *new_db, int 
*nmaps, const char *old_pgdata,
                                                          const char 
*new_pgdata);
 void           get_db_and_rel_infos(ClusterInfo *cluster);
+void           get_non_default_acl_infos(ClusterInfo *cluster);
 void           print_maps(FileNameMap *maps, int n,
                                           const char *db_name);

Reply via email to