| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * Utility routines for SQL dumping |
| 4 | * |
| 5 | * Basically this is stuff that is useful in both pg_dump and pg_dumpall. |
| 6 | * |
| 7 | * |
| 8 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 9 | * Portions Copyright (c) 1994, Regents of the University of California |
| 10 | * |
| 11 | * src/bin/pg_dump/dumputils.c |
| 12 | * |
| 13 | *------------------------------------------------------------------------- |
| 14 | */ |
| 15 | #include "postgres_fe.h" |
| 16 | |
| 17 | #include <ctype.h> |
| 18 | |
| 19 | #include "dumputils.h" |
| 20 | #include "fe_utils/string_utils.h" |
| 21 | |
| 22 | |
| 23 | static bool parseAclItem(const char *item, const char *type, |
| 24 | const char *name, const char *subname, int remoteVersion, |
| 25 | PQExpBuffer grantee, PQExpBuffer grantor, |
| 26 | PQExpBuffer privs, PQExpBuffer privswgo); |
| 27 | static char *copyAclUserName(PQExpBuffer output, char *input); |
| 28 | static void AddAcl(PQExpBuffer aclbuf, const char *keyword, |
| 29 | const char *subname); |
| 30 | |
| 31 | |
| 32 | /* |
| 33 | * Build GRANT/REVOKE command(s) for an object. |
| 34 | * |
| 35 | * name: the object name, in the form to use in the commands (already quoted) |
| 36 | * subname: the sub-object name, if any (already quoted); NULL if none |
| 37 | * nspname: the namespace the object is in (NULL if none); not pre-quoted |
| 38 | * type: the object type (as seen in GRANT command: must be one of |
| 39 | * TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE, |
| 40 | * FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT) |
| 41 | * acls: the ACL string fetched from the database |
| 42 | * racls: the ACL string of any initial-but-now-revoked privileges |
| 43 | * owner: username of object owner (will be passed through fmtId); can be |
| 44 | * NULL or empty string to indicate "no owner known" |
| 45 | * prefix: string to prefix to each generated command; typically empty |
| 46 | * remoteVersion: version of database |
| 47 | * |
| 48 | * Returns true if okay, false if could not parse the acl string. |
| 49 | * The resulting commands (if any) are appended to the contents of 'sql'. |
| 50 | * |
| 51 | * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES " |
| 52 | * or something similar, and name is an empty string. |
| 53 | * |
| 54 | * Note: beware of passing a fmtId() result directly as 'name' or 'subname', |
| 55 | * since this routine uses fmtId() internally. |
| 56 | */ |
| 57 | bool |
| 58 | buildACLCommands(const char *name, const char *subname, const char *nspname, |
| 59 | const char *type, const char *acls, const char *racls, |
| 60 | const char *owner, const char *prefix, int remoteVersion, |
| 61 | PQExpBuffer sql) |
| 62 | { |
| 63 | bool ok = true; |
| 64 | char **aclitems = NULL; |
| 65 | char **raclitems = NULL; |
| 66 | int naclitems = 0; |
| 67 | int nraclitems = 0; |
| 68 | int i; |
| 69 | PQExpBuffer grantee, |
| 70 | grantor, |
| 71 | privs, |
| 72 | privswgo; |
| 73 | PQExpBuffer firstsql, |
| 74 | secondsql; |
| 75 | bool found_owner_privs = false; |
| 76 | |
| 77 | if (strlen(acls) == 0 && strlen(racls) == 0) |
| 78 | return true; /* object has default permissions */ |
| 79 | |
| 80 | /* treat empty-string owner same as NULL */ |
| 81 | if (owner && *owner == '\0') |
| 82 | owner = NULL; |
| 83 | |
| 84 | if (strlen(acls) != 0) |
| 85 | { |
| 86 | if (!parsePGArray(acls, &aclitems, &naclitems)) |
| 87 | { |
| 88 | if (aclitems) |
| 89 | free(aclitems); |
| 90 | return false; |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | if (strlen(racls) != 0) |
| 95 | { |
| 96 | if (!parsePGArray(racls, &raclitems, &nraclitems)) |
| 97 | { |
| 98 | if (aclitems) |
| 99 | free(aclitems); |
| 100 | if (raclitems) |
| 101 | free(raclitems); |
| 102 | return false; |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | grantee = createPQExpBuffer(); |
| 107 | grantor = createPQExpBuffer(); |
| 108 | privs = createPQExpBuffer(); |
| 109 | privswgo = createPQExpBuffer(); |
| 110 | |
| 111 | /* |
| 112 | * At the end, these two will be pasted together to form the result. |
| 113 | * |
| 114 | * For older systems we use these to ensure that the owner privileges go |
| 115 | * before the other ones, as a GRANT could create the default entry for |
| 116 | * the object, which generally includes all rights for the owner. In more |
| 117 | * recent versions we normally handle this because the owner rights come |
| 118 | * first in the ACLs, but older versions might have them after the PUBLIC |
| 119 | * privileges. |
| 120 | * |
| 121 | * For 9.6 and later systems, much of this changes. With 9.6, we check |
| 122 | * the default privileges for the objects at dump time and create two sets |
| 123 | * of ACLs- "racls" which are the ACLs to REVOKE from the object (as the |
| 124 | * object may have initial privileges on it, along with any default ACLs |
| 125 | * which are not part of the current set of privileges), and regular |
| 126 | * "acls", which are the ACLs to GRANT to the object. We handle the |
| 127 | * REVOKEs first, followed by the GRANTs. |
| 128 | */ |
| 129 | firstsql = createPQExpBuffer(); |
| 130 | secondsql = createPQExpBuffer(); |
| 131 | |
| 132 | /* |
| 133 | * For pre-9.6 systems, we always start with REVOKE ALL FROM PUBLIC, as we |
| 134 | * don't wish to make any assumptions about what the default ACLs are, and |
| 135 | * we do not collect them during the dump phase (and racls will always be |
| 136 | * the empty set, see above). |
| 137 | * |
| 138 | * For 9.6 and later, if any revoke ACLs have been provided, then include |
| 139 | * them in 'firstsql'. |
| 140 | * |
| 141 | * Revoke ACLs happen when an object starts out life with a set of |
| 142 | * privileges (eg: GRANT SELECT ON pg_class TO PUBLIC;) and the user has |
| 143 | * decided to revoke those rights. Since those objects come into being |
| 144 | * with those default privileges, we have to revoke them to match what the |
| 145 | * current state of affairs is. Note that we only started explicitly |
| 146 | * tracking such initial rights in 9.6, and prior to that all initial |
| 147 | * rights are actually handled by the simple 'REVOKE ALL .. FROM PUBLIC' |
| 148 | * case, for initdb-created objects. Prior to 9.6, we didn't handle |
| 149 | * extensions correctly, but we do now by tracking their initial |
| 150 | * privileges, in the same way we track initdb initial privileges, see |
| 151 | * pg_init_privs. |
| 152 | */ |
| 153 | if (remoteVersion < 90600) |
| 154 | { |
| 155 | Assert(nraclitems == 0); |
| 156 | |
| 157 | appendPQExpBuffer(firstsql, "%sREVOKE ALL" , prefix); |
| 158 | if (subname) |
| 159 | appendPQExpBuffer(firstsql, "(%s)" , subname); |
| 160 | appendPQExpBuffer(firstsql, " ON %s " , type); |
| 161 | if (nspname && *nspname) |
| 162 | appendPQExpBuffer(firstsql, "%s." , fmtId(nspname)); |
| 163 | appendPQExpBuffer(firstsql, "%s FROM PUBLIC;\n" , name); |
| 164 | } |
| 165 | else |
| 166 | { |
| 167 | /* Scan individual REVOKE ACL items */ |
| 168 | for (i = 0; i < nraclitems; i++) |
| 169 | { |
| 170 | if (!parseAclItem(raclitems[i], type, name, subname, remoteVersion, |
| 171 | grantee, grantor, privs, privswgo)) |
| 172 | { |
| 173 | ok = false; |
| 174 | break; |
| 175 | } |
| 176 | |
| 177 | if (privs->len > 0 || privswgo->len > 0) |
| 178 | { |
| 179 | if (privs->len > 0) |
| 180 | { |
| 181 | appendPQExpBuffer(firstsql, "%sREVOKE %s ON %s " , |
| 182 | prefix, privs->data, type); |
| 183 | if (nspname && *nspname) |
| 184 | appendPQExpBuffer(firstsql, "%s." , fmtId(nspname)); |
| 185 | appendPQExpBuffer(firstsql, "%s FROM " , name); |
| 186 | if (grantee->len == 0) |
| 187 | appendPQExpBufferStr(firstsql, "PUBLIC;\n" ); |
| 188 | else if (strncmp(grantee->data, "group " , |
| 189 | strlen("group " )) == 0) |
| 190 | appendPQExpBuffer(firstsql, "GROUP %s;\n" , |
| 191 | fmtId(grantee->data + strlen("group " ))); |
| 192 | else |
| 193 | appendPQExpBuffer(firstsql, "%s;\n" , |
| 194 | fmtId(grantee->data)); |
| 195 | } |
| 196 | if (privswgo->len > 0) |
| 197 | { |
| 198 | appendPQExpBuffer(firstsql, |
| 199 | "%sREVOKE GRANT OPTION FOR %s ON %s " , |
| 200 | prefix, privswgo->data, type); |
| 201 | if (nspname && *nspname) |
| 202 | appendPQExpBuffer(firstsql, "%s." , fmtId(nspname)); |
| 203 | appendPQExpBuffer(firstsql, "%s FROM " , name); |
| 204 | if (grantee->len == 0) |
| 205 | appendPQExpBufferStr(firstsql, "PUBLIC" ); |
| 206 | else if (strncmp(grantee->data, "group " , |
| 207 | strlen("group " )) == 0) |
| 208 | appendPQExpBuffer(firstsql, "GROUP %s" , |
| 209 | fmtId(grantee->data + strlen("group " ))); |
| 210 | else |
| 211 | appendPQExpBufferStr(firstsql, fmtId(grantee->data)); |
| 212 | } |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | /* |
| 218 | * We still need some hacking though to cover the case where new default |
| 219 | * public privileges are added in new versions: the REVOKE ALL will revoke |
| 220 | * them, leading to behavior different from what the old version had, |
| 221 | * which is generally not what's wanted. So add back default privs if the |
| 222 | * source database is too old to have had that particular priv. |
| 223 | */ |
| 224 | if (remoteVersion < 80200 && strcmp(type, "DATABASE" ) == 0) |
| 225 | { |
| 226 | /* database CONNECT priv didn't exist before 8.2 */ |
| 227 | appendPQExpBuffer(firstsql, "%sGRANT CONNECT ON %s %s TO PUBLIC;\n" , |
| 228 | prefix, type, name); |
| 229 | } |
| 230 | |
| 231 | /* Scan individual ACL items */ |
| 232 | for (i = 0; i < naclitems; i++) |
| 233 | { |
| 234 | if (!parseAclItem(aclitems[i], type, name, subname, remoteVersion, |
| 235 | grantee, grantor, privs, privswgo)) |
| 236 | { |
| 237 | ok = false; |
| 238 | break; |
| 239 | } |
| 240 | |
| 241 | if (grantor->len == 0 && owner) |
| 242 | printfPQExpBuffer(grantor, "%s" , owner); |
| 243 | |
| 244 | if (privs->len > 0 || privswgo->len > 0) |
| 245 | { |
| 246 | /* |
| 247 | * Prior to 9.6, we had to handle owner privileges in a special |
| 248 | * manner by first REVOKE'ing the rights and then GRANT'ing them |
| 249 | * after. With 9.6 and above, what we need to REVOKE and what we |
| 250 | * need to GRANT is figured out when we dump and stashed into |
| 251 | * "racls" and "acls", respectively. See above. |
| 252 | */ |
| 253 | if (remoteVersion < 90600 && owner |
| 254 | && strcmp(grantee->data, owner) == 0 |
| 255 | && strcmp(grantor->data, owner) == 0) |
| 256 | { |
| 257 | found_owner_privs = true; |
| 258 | |
| 259 | /* |
| 260 | * For the owner, the default privilege level is ALL WITH |
| 261 | * GRANT OPTION. |
| 262 | */ |
| 263 | if (strcmp(privswgo->data, "ALL" ) != 0) |
| 264 | { |
| 265 | appendPQExpBuffer(firstsql, "%sREVOKE ALL" , prefix); |
| 266 | if (subname) |
| 267 | appendPQExpBuffer(firstsql, "(%s)" , subname); |
| 268 | appendPQExpBuffer(firstsql, " ON %s " , type); |
| 269 | if (nspname && *nspname) |
| 270 | appendPQExpBuffer(firstsql, "%s." , fmtId(nspname)); |
| 271 | appendPQExpBuffer(firstsql, "%s FROM %s;\n" , |
| 272 | name, fmtId(grantee->data)); |
| 273 | if (privs->len > 0) |
| 274 | { |
| 275 | appendPQExpBuffer(firstsql, |
| 276 | "%sGRANT %s ON %s " , |
| 277 | prefix, privs->data, type); |
| 278 | if (nspname && *nspname) |
| 279 | appendPQExpBuffer(firstsql, "%s." , fmtId(nspname)); |
| 280 | appendPQExpBuffer(firstsql, |
| 281 | "%s TO %s;\n" , |
| 282 | name, fmtId(grantee->data)); |
| 283 | } |
| 284 | if (privswgo->len > 0) |
| 285 | { |
| 286 | appendPQExpBuffer(firstsql, |
| 287 | "%sGRANT %s ON %s " , |
| 288 | prefix, privswgo->data, type); |
| 289 | if (nspname && *nspname) |
| 290 | appendPQExpBuffer(firstsql, "%s." , fmtId(nspname)); |
| 291 | appendPQExpBuffer(firstsql, |
| 292 | "%s TO %s WITH GRANT OPTION;\n" , |
| 293 | name, fmtId(grantee->data)); |
| 294 | } |
| 295 | } |
| 296 | } |
| 297 | else |
| 298 | { |
| 299 | /* |
| 300 | * For systems prior to 9.6, we can assume we are starting |
| 301 | * from no privs at this point. |
| 302 | * |
| 303 | * For 9.6 and above, at this point we have issued REVOKE |
| 304 | * statements for all initial and default privileges which are |
| 305 | * no longer present on the object (as they were passed in as |
| 306 | * 'racls') and we can simply GRANT the rights which are in |
| 307 | * 'acls'. |
| 308 | */ |
| 309 | if (grantor->len > 0 |
| 310 | && (!owner || strcmp(owner, grantor->data) != 0)) |
| 311 | appendPQExpBuffer(secondsql, "SET SESSION AUTHORIZATION %s;\n" , |
| 312 | fmtId(grantor->data)); |
| 313 | |
| 314 | if (privs->len > 0) |
| 315 | { |
| 316 | appendPQExpBuffer(secondsql, "%sGRANT %s ON %s " , |
| 317 | prefix, privs->data, type); |
| 318 | if (nspname && *nspname) |
| 319 | appendPQExpBuffer(secondsql, "%s." , fmtId(nspname)); |
| 320 | appendPQExpBuffer(secondsql, "%s TO " , name); |
| 321 | if (grantee->len == 0) |
| 322 | appendPQExpBufferStr(secondsql, "PUBLIC;\n" ); |
| 323 | else if (strncmp(grantee->data, "group " , |
| 324 | strlen("group " )) == 0) |
| 325 | appendPQExpBuffer(secondsql, "GROUP %s;\n" , |
| 326 | fmtId(grantee->data + strlen("group " ))); |
| 327 | else |
| 328 | appendPQExpBuffer(secondsql, "%s;\n" , fmtId(grantee->data)); |
| 329 | } |
| 330 | if (privswgo->len > 0) |
| 331 | { |
| 332 | appendPQExpBuffer(secondsql, "%sGRANT %s ON %s " , |
| 333 | prefix, privswgo->data, type); |
| 334 | if (nspname && *nspname) |
| 335 | appendPQExpBuffer(secondsql, "%s." , fmtId(nspname)); |
| 336 | appendPQExpBuffer(secondsql, "%s TO " , name); |
| 337 | if (grantee->len == 0) |
| 338 | appendPQExpBufferStr(secondsql, "PUBLIC" ); |
| 339 | else if (strncmp(grantee->data, "group " , |
| 340 | strlen("group " )) == 0) |
| 341 | appendPQExpBuffer(secondsql, "GROUP %s" , |
| 342 | fmtId(grantee->data + strlen("group " ))); |
| 343 | else |
| 344 | appendPQExpBufferStr(secondsql, fmtId(grantee->data)); |
| 345 | appendPQExpBufferStr(secondsql, " WITH GRANT OPTION;\n" ); |
| 346 | } |
| 347 | |
| 348 | if (grantor->len > 0 |
| 349 | && (!owner || strcmp(owner, grantor->data) != 0)) |
| 350 | appendPQExpBufferStr(secondsql, "RESET SESSION AUTHORIZATION;\n" ); |
| 351 | } |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | /* |
| 356 | * For systems prior to 9.6, if we didn't find any owner privs, the owner |
| 357 | * must have revoked 'em all. |
| 358 | * |
| 359 | * For 9.6 and above, we handle this through the 'racls'. See above. |
| 360 | */ |
| 361 | if (remoteVersion < 90600 && !found_owner_privs && owner) |
| 362 | { |
| 363 | appendPQExpBuffer(firstsql, "%sREVOKE ALL" , prefix); |
| 364 | if (subname) |
| 365 | appendPQExpBuffer(firstsql, "(%s)" , subname); |
| 366 | appendPQExpBuffer(firstsql, " ON %s " , type); |
| 367 | if (nspname && *nspname) |
| 368 | appendPQExpBuffer(firstsql, "%s." , fmtId(nspname)); |
| 369 | appendPQExpBuffer(firstsql, "%s FROM %s;\n" , |
| 370 | name, fmtId(owner)); |
| 371 | } |
| 372 | |
| 373 | destroyPQExpBuffer(grantee); |
| 374 | destroyPQExpBuffer(grantor); |
| 375 | destroyPQExpBuffer(privs); |
| 376 | destroyPQExpBuffer(privswgo); |
| 377 | |
| 378 | appendPQExpBuffer(sql, "%s%s" , firstsql->data, secondsql->data); |
| 379 | destroyPQExpBuffer(firstsql); |
| 380 | destroyPQExpBuffer(secondsql); |
| 381 | |
| 382 | if (aclitems) |
| 383 | free(aclitems); |
| 384 | |
| 385 | if (raclitems) |
| 386 | free(raclitems); |
| 387 | |
| 388 | return ok; |
| 389 | } |
| 390 | |
| 391 | /* |
| 392 | * Build ALTER DEFAULT PRIVILEGES command(s) for single pg_default_acl entry. |
| 393 | * |
| 394 | * type: the object type (TABLES, FUNCTIONS, etc) |
| 395 | * nspname: schema name, or NULL for global default privileges |
| 396 | * acls: the ACL string fetched from the database |
| 397 | * owner: username of privileges owner (will be passed through fmtId) |
| 398 | * remoteVersion: version of database |
| 399 | * |
| 400 | * Returns true if okay, false if could not parse the acl string. |
| 401 | * The resulting commands (if any) are appended to the contents of 'sql'. |
| 402 | */ |
| 403 | bool |
| 404 | buildDefaultACLCommands(const char *type, const char *nspname, |
| 405 | const char *acls, const char *racls, |
| 406 | const char *initacls, const char *initracls, |
| 407 | const char *owner, |
| 408 | int remoteVersion, |
| 409 | PQExpBuffer sql) |
| 410 | { |
| 411 | PQExpBuffer prefix; |
| 412 | |
| 413 | prefix = createPQExpBuffer(); |
| 414 | |
| 415 | /* |
| 416 | * We incorporate the target role directly into the command, rather than |
| 417 | * playing around with SET ROLE or anything like that. This is so that a |
| 418 | * permissions error leads to nothing happening, rather than changing |
| 419 | * default privileges for the wrong user. |
| 420 | */ |
| 421 | appendPQExpBuffer(prefix, "ALTER DEFAULT PRIVILEGES FOR ROLE %s " , |
| 422 | fmtId(owner)); |
| 423 | if (nspname) |
| 424 | appendPQExpBuffer(prefix, "IN SCHEMA %s " , fmtId(nspname)); |
| 425 | |
| 426 | if (strlen(initacls) != 0 || strlen(initracls) != 0) |
| 427 | { |
| 428 | appendPQExpBuffer(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\n" ); |
| 429 | if (!buildACLCommands("" , NULL, NULL, type, |
| 430 | initacls, initracls, owner, |
| 431 | prefix->data, remoteVersion, sql)) |
| 432 | { |
| 433 | destroyPQExpBuffer(prefix); |
| 434 | return false; |
| 435 | } |
| 436 | appendPQExpBuffer(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\n" ); |
| 437 | } |
| 438 | |
| 439 | if (!buildACLCommands("" , NULL, NULL, type, |
| 440 | acls, racls, owner, |
| 441 | prefix->data, remoteVersion, sql)) |
| 442 | { |
| 443 | destroyPQExpBuffer(prefix); |
| 444 | return false; |
| 445 | } |
| 446 | |
| 447 | destroyPQExpBuffer(prefix); |
| 448 | |
| 449 | return true; |
| 450 | } |
| 451 | |
| 452 | /* |
| 453 | * This will parse an aclitem string, having the general form |
| 454 | * username=privilegecodes/grantor |
| 455 | * or |
| 456 | * group groupname=privilegecodes/grantor |
| 457 | * (the "group" case occurs only with servers before 8.1). |
| 458 | * |
| 459 | * Returns true on success, false on parse error. On success, the components |
| 460 | * of the string are returned in the PQExpBuffer parameters. |
| 461 | * |
| 462 | * The returned grantee string will be the dequoted username or groupname |
| 463 | * (preceded with "group " in the latter case). Note that a grant to PUBLIC |
| 464 | * is represented by an empty grantee string. The returned grantor is the |
| 465 | * dequoted grantor name. Privilege characters are decoded and split between |
| 466 | * privileges with grant option (privswgo) and without (privs). |
| 467 | * |
| 468 | * Note: for cross-version compatibility, it's important to use ALL to |
| 469 | * represent the privilege sets whenever appropriate. |
| 470 | */ |
| 471 | static bool |
| 472 | parseAclItem(const char *item, const char *type, |
| 473 | const char *name, const char *subname, int remoteVersion, |
| 474 | PQExpBuffer grantee, PQExpBuffer grantor, |
| 475 | PQExpBuffer privs, PQExpBuffer privswgo) |
| 476 | { |
| 477 | char *buf; |
| 478 | bool all_with_go = true; |
| 479 | bool all_without_go = true; |
| 480 | char *eqpos; |
| 481 | char *slpos; |
| 482 | char *pos; |
| 483 | |
| 484 | buf = pg_strdup(item); |
| 485 | |
| 486 | /* user or group name is string up to = */ |
| 487 | eqpos = copyAclUserName(grantee, buf); |
| 488 | if (*eqpos != '=') |
| 489 | { |
| 490 | pg_free(buf); |
| 491 | return false; |
| 492 | } |
| 493 | |
| 494 | /* grantor should appear after / */ |
| 495 | slpos = strchr(eqpos + 1, '/'); |
| 496 | if (slpos) |
| 497 | { |
| 498 | *slpos++ = '\0'; |
| 499 | slpos = copyAclUserName(grantor, slpos); |
| 500 | if (*slpos != '\0') |
| 501 | { |
| 502 | pg_free(buf); |
| 503 | return false; |
| 504 | } |
| 505 | } |
| 506 | else |
| 507 | { |
| 508 | pg_free(buf); |
| 509 | return false; |
| 510 | } |
| 511 | |
| 512 | /* privilege codes */ |
| 513 | #define CONVERT_PRIV(code, keywd) \ |
| 514 | do { \ |
| 515 | if ((pos = strchr(eqpos + 1, code))) \ |
| 516 | { \ |
| 517 | if (*(pos + 1) == '*') \ |
| 518 | { \ |
| 519 | AddAcl(privswgo, keywd, subname); \ |
| 520 | all_without_go = false; \ |
| 521 | } \ |
| 522 | else \ |
| 523 | { \ |
| 524 | AddAcl(privs, keywd, subname); \ |
| 525 | all_with_go = false; \ |
| 526 | } \ |
| 527 | } \ |
| 528 | else \ |
| 529 | all_with_go = all_without_go = false; \ |
| 530 | } while (0) |
| 531 | |
| 532 | resetPQExpBuffer(privs); |
| 533 | resetPQExpBuffer(privswgo); |
| 534 | |
| 535 | if (strcmp(type, "TABLE" ) == 0 || strcmp(type, "SEQUENCE" ) == 0 || |
| 536 | strcmp(type, "TABLES" ) == 0 || strcmp(type, "SEQUENCES" ) == 0) |
| 537 | { |
| 538 | CONVERT_PRIV('r', "SELECT" ); |
| 539 | |
| 540 | if (strcmp(type, "SEQUENCE" ) == 0 || |
| 541 | strcmp(type, "SEQUENCES" ) == 0) |
| 542 | /* sequence only */ |
| 543 | CONVERT_PRIV('U', "USAGE" ); |
| 544 | else |
| 545 | { |
| 546 | /* table only */ |
| 547 | CONVERT_PRIV('a', "INSERT" ); |
| 548 | CONVERT_PRIV('x', "REFERENCES" ); |
| 549 | /* rest are not applicable to columns */ |
| 550 | if (subname == NULL) |
| 551 | { |
| 552 | CONVERT_PRIV('d', "DELETE" ); |
| 553 | CONVERT_PRIV('t', "TRIGGER" ); |
| 554 | if (remoteVersion >= 80400) |
| 555 | CONVERT_PRIV('D', "TRUNCATE" ); |
| 556 | } |
| 557 | } |
| 558 | |
| 559 | /* UPDATE */ |
| 560 | CONVERT_PRIV('w', "UPDATE" ); |
| 561 | } |
| 562 | else if (strcmp(type, "FUNCTION" ) == 0 || |
| 563 | strcmp(type, "FUNCTIONS" ) == 0) |
| 564 | CONVERT_PRIV('X', "EXECUTE" ); |
| 565 | else if (strcmp(type, "PROCEDURE" ) == 0 || |
| 566 | strcmp(type, "PROCEDURES" ) == 0) |
| 567 | CONVERT_PRIV('X', "EXECUTE" ); |
| 568 | else if (strcmp(type, "LANGUAGE" ) == 0) |
| 569 | CONVERT_PRIV('U', "USAGE" ); |
| 570 | else if (strcmp(type, "SCHEMA" ) == 0 || |
| 571 | strcmp(type, "SCHEMAS" ) == 0) |
| 572 | { |
| 573 | CONVERT_PRIV('C', "CREATE" ); |
| 574 | CONVERT_PRIV('U', "USAGE" ); |
| 575 | } |
| 576 | else if (strcmp(type, "DATABASE" ) == 0) |
| 577 | { |
| 578 | CONVERT_PRIV('C', "CREATE" ); |
| 579 | CONVERT_PRIV('c', "CONNECT" ); |
| 580 | CONVERT_PRIV('T', "TEMPORARY" ); |
| 581 | } |
| 582 | else if (strcmp(type, "TABLESPACE" ) == 0) |
| 583 | CONVERT_PRIV('C', "CREATE" ); |
| 584 | else if (strcmp(type, "TYPE" ) == 0 || |
| 585 | strcmp(type, "TYPES" ) == 0) |
| 586 | CONVERT_PRIV('U', "USAGE" ); |
| 587 | else if (strcmp(type, "FOREIGN DATA WRAPPER" ) == 0) |
| 588 | CONVERT_PRIV('U', "USAGE" ); |
| 589 | else if (strcmp(type, "FOREIGN SERVER" ) == 0) |
| 590 | CONVERT_PRIV('U', "USAGE" ); |
| 591 | else if (strcmp(type, "FOREIGN TABLE" ) == 0) |
| 592 | CONVERT_PRIV('r', "SELECT" ); |
| 593 | else if (strcmp(type, "LARGE OBJECT" ) == 0) |
| 594 | { |
| 595 | CONVERT_PRIV('r', "SELECT" ); |
| 596 | CONVERT_PRIV('w', "UPDATE" ); |
| 597 | } |
| 598 | else |
| 599 | abort(); |
| 600 | |
| 601 | #undef CONVERT_PRIV |
| 602 | |
| 603 | if (all_with_go) |
| 604 | { |
| 605 | resetPQExpBuffer(privs); |
| 606 | printfPQExpBuffer(privswgo, "ALL" ); |
| 607 | if (subname) |
| 608 | appendPQExpBuffer(privswgo, "(%s)" , subname); |
| 609 | } |
| 610 | else if (all_without_go) |
| 611 | { |
| 612 | resetPQExpBuffer(privswgo); |
| 613 | printfPQExpBuffer(privs, "ALL" ); |
| 614 | if (subname) |
| 615 | appendPQExpBuffer(privs, "(%s)" , subname); |
| 616 | } |
| 617 | |
| 618 | pg_free(buf); |
| 619 | |
| 620 | return true; |
| 621 | } |
| 622 | |
| 623 | /* |
| 624 | * Transfer a user or group name starting at *input into the output buffer, |
| 625 | * dequoting if needed. Returns a pointer to just past the input name. |
| 626 | * The name is taken to end at an unquoted '=' or end of string. |
| 627 | */ |
| 628 | static char * |
| 629 | copyAclUserName(PQExpBuffer output, char *input) |
| 630 | { |
| 631 | resetPQExpBuffer(output); |
| 632 | |
| 633 | while (*input && *input != '=') |
| 634 | { |
| 635 | /* |
| 636 | * If user name isn't quoted, then just add it to the output buffer |
| 637 | */ |
| 638 | if (*input != '"') |
| 639 | appendPQExpBufferChar(output, *input++); |
| 640 | else |
| 641 | { |
| 642 | /* Otherwise, it's a quoted username */ |
| 643 | input++; |
| 644 | /* Loop until we come across an unescaped quote */ |
| 645 | while (!(*input == '"' && *(input + 1) != '"')) |
| 646 | { |
| 647 | if (*input == '\0') |
| 648 | return input; /* really a syntax error... */ |
| 649 | |
| 650 | /* |
| 651 | * Quoting convention is to escape " as "". Keep this code in |
| 652 | * sync with putid() in backend's acl.c. |
| 653 | */ |
| 654 | if (*input == '"' && *(input + 1) == '"') |
| 655 | input++; |
| 656 | appendPQExpBufferChar(output, *input++); |
| 657 | } |
| 658 | input++; |
| 659 | } |
| 660 | } |
| 661 | return input; |
| 662 | } |
| 663 | |
| 664 | /* |
| 665 | * Append a privilege keyword to a keyword list, inserting comma if needed. |
| 666 | */ |
| 667 | static void |
| 668 | AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname) |
| 669 | { |
| 670 | if (aclbuf->len > 0) |
| 671 | appendPQExpBufferChar(aclbuf, ','); |
| 672 | appendPQExpBufferStr(aclbuf, keyword); |
| 673 | if (subname) |
| 674 | appendPQExpBuffer(aclbuf, "(%s)" , subname); |
| 675 | } |
| 676 | |
| 677 | |
| 678 | /* |
| 679 | * buildShSecLabelQuery |
| 680 | * |
| 681 | * Build a query to retrieve security labels for a shared object. |
| 682 | * The object is identified by its OID plus the name of the catalog |
| 683 | * it can be found in (e.g., "pg_database" for database names). |
| 684 | * The query is appended to "sql". (We don't execute it here so as to |
| 685 | * keep this file free of assumptions about how to deal with SQL errors.) |
| 686 | */ |
| 687 | void |
| 688 | buildShSecLabelQuery(PGconn *conn, const char *catalog_name, Oid objectId, |
| 689 | PQExpBuffer sql) |
| 690 | { |
| 691 | appendPQExpBuffer(sql, |
| 692 | "SELECT provider, label FROM pg_catalog.pg_shseclabel " |
| 693 | "WHERE classoid = 'pg_catalog.%s'::pg_catalog.regclass " |
| 694 | "AND objoid = '%u'" , catalog_name, objectId); |
| 695 | } |
| 696 | |
| 697 | /* |
| 698 | * emitShSecLabels |
| 699 | * |
| 700 | * Construct SECURITY LABEL commands using the data retrieved by the query |
| 701 | * generated by buildShSecLabelQuery, and append them to "buffer". |
| 702 | * Here, the target object is identified by its type name (e.g. "DATABASE") |
| 703 | * and its name (not pre-quoted). |
| 704 | */ |
| 705 | void |
| 706 | emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer, |
| 707 | const char *objtype, const char *objname) |
| 708 | { |
| 709 | int i; |
| 710 | |
| 711 | for (i = 0; i < PQntuples(res); i++) |
| 712 | { |
| 713 | char *provider = PQgetvalue(res, i, 0); |
| 714 | char *label = PQgetvalue(res, i, 1); |
| 715 | |
| 716 | /* must use fmtId result before calling it again */ |
| 717 | appendPQExpBuffer(buffer, |
| 718 | "SECURITY LABEL FOR %s ON %s" , |
| 719 | fmtId(provider), objtype); |
| 720 | appendPQExpBuffer(buffer, |
| 721 | " %s IS " , |
| 722 | fmtId(objname)); |
| 723 | appendStringLiteralConn(buffer, label, conn); |
| 724 | appendPQExpBufferStr(buffer, ";\n" ); |
| 725 | } |
| 726 | } |
| 727 | |
| 728 | /* |
| 729 | * buildACLQueries |
| 730 | * |
| 731 | * Build the subqueries to extract out the correct set of ACLs to be |
| 732 | * GRANT'd and REVOKE'd for the specific kind of object, accounting for any |
| 733 | * initial privileges (from pg_init_privs) and based on if we are in binary |
| 734 | * upgrade mode or not. |
| 735 | * |
| 736 | * Also builds subqueries to extract out the set of ACLs to go from the object |
| 737 | * default privileges to the privileges in pg_init_privs, if we are in binary |
| 738 | * upgrade mode, so that those privileges can be set up and recorded in the new |
| 739 | * cluster before the regular privileges are added on top of those. |
| 740 | */ |
| 741 | void |
| 742 | buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery, |
| 743 | PQExpBuffer init_acl_subquery, PQExpBuffer init_racl_subquery, |
| 744 | const char *acl_column, const char *acl_owner, |
| 745 | const char *obj_kind, bool binary_upgrade) |
| 746 | { |
| 747 | /* |
| 748 | * To get the delta from what the permissions were at creation time |
| 749 | * (either initdb or CREATE EXTENSION) vs. what they are now, we have to |
| 750 | * look at two things: |
| 751 | * |
| 752 | * What privileges have been added, which we calculate by extracting all |
| 753 | * the current privileges (using the set of default privileges for the |
| 754 | * object type if current privileges are NULL) and then removing those |
| 755 | * which existed at creation time (again, using the set of default |
| 756 | * privileges for the object type if there were no creation time |
| 757 | * privileges). |
| 758 | * |
| 759 | * What privileges have been removed, which we calculate by extracting the |
| 760 | * privileges as they were at creation time (or the default privileges, as |
| 761 | * above), and then removing the current privileges (or the default |
| 762 | * privileges, if current privileges are NULL). |
| 763 | * |
| 764 | * As a good cross-check, both directions of these checks should result in |
| 765 | * the empty set if both the current ACL and the initial privs are NULL |
| 766 | * (meaning, in practice, that the default ACLs were there at init time |
| 767 | * and is what the current privileges are). |
| 768 | * |
| 769 | * We always perform this delta on all ACLs and expect that by the time |
| 770 | * these are run the initial privileges will be in place, even in a binary |
| 771 | * upgrade situation (see below). |
| 772 | * |
| 773 | * Finally, the order in which privileges are in the ACL string (the order |
| 774 | * they been GRANT'd in, which the backend maintains) must be preserved to |
| 775 | * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on |
| 776 | * those are dumped in the correct order. |
| 777 | */ |
| 778 | printfPQExpBuffer(acl_subquery, |
| 779 | "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM " |
| 780 | "(SELECT acl, row_n FROM " |
| 781 | "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) " |
| 782 | "WITH ORDINALITY AS perm(acl,row_n) " |
| 783 | "WHERE NOT EXISTS ( " |
| 784 | "SELECT 1 FROM " |
| 785 | "pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault(%s,%s))) " |
| 786 | "AS init(init_acl) WHERE acl = init_acl)) as foo)" , |
| 787 | acl_column, |
| 788 | obj_kind, |
| 789 | acl_owner, |
| 790 | obj_kind, |
| 791 | acl_owner); |
| 792 | |
| 793 | printfPQExpBuffer(racl_subquery, |
| 794 | "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM " |
| 795 | "(SELECT acl, row_n FROM " |
| 796 | "pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault(%s,%s))) " |
| 797 | "WITH ORDINALITY AS initp(acl,row_n) " |
| 798 | "WHERE NOT EXISTS ( " |
| 799 | "SELECT 1 FROM " |
| 800 | "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) " |
| 801 | "AS permp(orig_acl) WHERE acl = orig_acl)) as foo)" , |
| 802 | obj_kind, |
| 803 | acl_owner, |
| 804 | acl_column, |
| 805 | obj_kind, |
| 806 | acl_owner); |
| 807 | |
| 808 | /* |
| 809 | * In binary upgrade mode we don't run the extension script but instead |
| 810 | * dump out the objects independently and then recreate them. To preserve |
| 811 | * the initial privileges which were set on extension objects, we need to |
| 812 | * grab the set of GRANT and REVOKE commands necessary to get from the |
| 813 | * default privileges of an object to the initial privileges as recorded |
| 814 | * in pg_init_privs. |
| 815 | * |
| 816 | * These will then be run ahead of the regular ACL commands, which were |
| 817 | * calculated using the queries above, inside of a block which sets a flag |
| 818 | * to indicate that the backend should record the results of these GRANT |
| 819 | * and REVOKE statements into pg_init_privs. This is how we preserve the |
| 820 | * contents of that catalog across binary upgrades. |
| 821 | */ |
| 822 | if (binary_upgrade) |
| 823 | { |
| 824 | printfPQExpBuffer(init_acl_subquery, |
| 825 | "CASE WHEN privtype = 'e' THEN " |
| 826 | "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM " |
| 827 | "(SELECT acl, row_n FROM pg_catalog.unnest(pip.initprivs) " |
| 828 | "WITH ORDINALITY AS initp(acl,row_n) " |
| 829 | "WHERE NOT EXISTS ( " |
| 830 | "SELECT 1 FROM " |
| 831 | "pg_catalog.unnest(pg_catalog.acldefault(%s,%s)) " |
| 832 | "AS privm(orig_acl) WHERE acl = orig_acl)) as foo) END" , |
| 833 | obj_kind, |
| 834 | acl_owner); |
| 835 | |
| 836 | printfPQExpBuffer(init_racl_subquery, |
| 837 | "CASE WHEN privtype = 'e' THEN " |
| 838 | "(SELECT pg_catalog.array_agg(acl) FROM " |
| 839 | "(SELECT acl, row_n FROM " |
| 840 | "pg_catalog.unnest(pg_catalog.acldefault(%s,%s)) " |
| 841 | "WITH ORDINALITY AS privp(acl,row_n) " |
| 842 | "WHERE NOT EXISTS ( " |
| 843 | "SELECT 1 FROM pg_catalog.unnest(pip.initprivs) " |
| 844 | "AS initp(init_acl) WHERE acl = init_acl)) as foo) END" , |
| 845 | obj_kind, |
| 846 | acl_owner); |
| 847 | } |
| 848 | else |
| 849 | { |
| 850 | printfPQExpBuffer(init_acl_subquery, "NULL" ); |
| 851 | printfPQExpBuffer(init_racl_subquery, "NULL" ); |
| 852 | } |
| 853 | } |
| 854 | |
| 855 | /* |
| 856 | * Detect whether the given GUC variable is of GUC_LIST_QUOTE type. |
| 857 | * |
| 858 | * It'd be better if we could inquire this directly from the backend; but even |
| 859 | * if there were a function for that, it could only tell us about variables |
| 860 | * currently known to guc.c, so that it'd be unsafe for extensions to declare |
| 861 | * GUC_LIST_QUOTE variables anyway. Lacking a solution for that, it doesn't |
| 862 | * seem worth the work to do more than have this list, which must be kept in |
| 863 | * sync with the variables actually marked GUC_LIST_QUOTE in guc.c. |
| 864 | */ |
| 865 | bool |
| 866 | variable_is_guc_list_quote(const char *name) |
| 867 | { |
| 868 | if (pg_strcasecmp(name, "temp_tablespaces" ) == 0 || |
| 869 | pg_strcasecmp(name, "session_preload_libraries" ) == 0 || |
| 870 | pg_strcasecmp(name, "shared_preload_libraries" ) == 0 || |
| 871 | pg_strcasecmp(name, "local_preload_libraries" ) == 0 || |
| 872 | pg_strcasecmp(name, "search_path" ) == 0) |
| 873 | return true; |
| 874 | else |
| 875 | return false; |
| 876 | } |
| 877 | |
| 878 | /* |
| 879 | * SplitGUCList --- parse a string containing identifiers or file names |
| 880 | * |
| 881 | * This is used to split the value of a GUC_LIST_QUOTE GUC variable, without |
| 882 | * presuming whether the elements will be taken as identifiers or file names. |
| 883 | * See comparable code in src/backend/utils/adt/varlena.c. |
| 884 | * |
| 885 | * Inputs: |
| 886 | * rawstring: the input string; must be overwritable! On return, it's |
| 887 | * been modified to contain the separated identifiers. |
| 888 | * separator: the separator punctuation expected between identifiers |
| 889 | * (typically '.' or ','). Whitespace may also appear around |
| 890 | * identifiers. |
| 891 | * Outputs: |
| 892 | * namelist: receives a malloc'd, null-terminated array of pointers to |
| 893 | * identifiers within rawstring. Caller should free this |
| 894 | * even on error return. |
| 895 | * |
| 896 | * Returns true if okay, false if there is a syntax error in the string. |
| 897 | */ |
| 898 | bool |
| 899 | SplitGUCList(char *rawstring, char separator, |
| 900 | char ***namelist) |
| 901 | { |
| 902 | char *nextp = rawstring; |
| 903 | bool done = false; |
| 904 | char **nextptr; |
| 905 | |
| 906 | /* |
| 907 | * Since we disallow empty identifiers, this is a conservative |
| 908 | * overestimate of the number of pointers we could need. Allow one for |
| 909 | * list terminator. |
| 910 | */ |
| 911 | *namelist = nextptr = (char **) |
| 912 | pg_malloc((strlen(rawstring) / 2 + 2) * sizeof(char *)); |
| 913 | *nextptr = NULL; |
| 914 | |
| 915 | while (isspace((unsigned char) *nextp)) |
| 916 | nextp++; /* skip leading whitespace */ |
| 917 | |
| 918 | if (*nextp == '\0') |
| 919 | return true; /* allow empty string */ |
| 920 | |
| 921 | /* At the top of the loop, we are at start of a new identifier. */ |
| 922 | do |
| 923 | { |
| 924 | char *curname; |
| 925 | char *endp; |
| 926 | |
| 927 | if (*nextp == '"') |
| 928 | { |
| 929 | /* Quoted name --- collapse quote-quote pairs */ |
| 930 | curname = nextp + 1; |
| 931 | for (;;) |
| 932 | { |
| 933 | endp = strchr(nextp + 1, '"'); |
| 934 | if (endp == NULL) |
| 935 | return false; /* mismatched quotes */ |
| 936 | if (endp[1] != '"') |
| 937 | break; /* found end of quoted name */ |
| 938 | /* Collapse adjacent quotes into one quote, and look again */ |
| 939 | memmove(endp, endp + 1, strlen(endp)); |
| 940 | nextp = endp; |
| 941 | } |
| 942 | /* endp now points at the terminating quote */ |
| 943 | nextp = endp + 1; |
| 944 | } |
| 945 | else |
| 946 | { |
| 947 | /* Unquoted name --- extends to separator or whitespace */ |
| 948 | curname = nextp; |
| 949 | while (*nextp && *nextp != separator && |
| 950 | !isspace((unsigned char) *nextp)) |
| 951 | nextp++; |
| 952 | endp = nextp; |
| 953 | if (curname == nextp) |
| 954 | return false; /* empty unquoted name not allowed */ |
| 955 | } |
| 956 | |
| 957 | while (isspace((unsigned char) *nextp)) |
| 958 | nextp++; /* skip trailing whitespace */ |
| 959 | |
| 960 | if (*nextp == separator) |
| 961 | { |
| 962 | nextp++; |
| 963 | while (isspace((unsigned char) *nextp)) |
| 964 | nextp++; /* skip leading whitespace for next */ |
| 965 | /* we expect another name, so done remains false */ |
| 966 | } |
| 967 | else if (*nextp == '\0') |
| 968 | done = true; |
| 969 | else |
| 970 | return false; /* invalid syntax */ |
| 971 | |
| 972 | /* Now safe to overwrite separator with a null */ |
| 973 | *endp = '\0'; |
| 974 | |
| 975 | /* |
| 976 | * Finished isolating current name --- add it to output array |
| 977 | */ |
| 978 | *nextptr++ = curname; |
| 979 | |
| 980 | /* Loop back if we didn't reach end of string */ |
| 981 | } while (!done); |
| 982 | |
| 983 | *nextptr = NULL; |
| 984 | return true; |
| 985 | } |
| 986 | |
| 987 | /* |
| 988 | * Helper function for dumping "ALTER DATABASE/ROLE SET ..." commands. |
| 989 | * |
| 990 | * Parse the contents of configitem (a "name=value" string), wrap it in |
| 991 | * a complete ALTER command, and append it to buf. |
| 992 | * |
| 993 | * type is DATABASE or ROLE, and name is the name of the database or role. |
| 994 | * If we need an "IN" clause, type2 and name2 similarly define what to put |
| 995 | * there; otherwise they should be NULL. |
| 996 | * conn is used only to determine string-literal quoting conventions. |
| 997 | */ |
| 998 | void |
| 999 | makeAlterConfigCommand(PGconn *conn, const char *configitem, |
| 1000 | const char *type, const char *name, |
| 1001 | const char *type2, const char *name2, |
| 1002 | PQExpBuffer buf) |
| 1003 | { |
| 1004 | char *mine; |
| 1005 | char *pos; |
| 1006 | |
| 1007 | /* Parse the configitem. If we can't find an "=", silently do nothing. */ |
| 1008 | mine = pg_strdup(configitem); |
| 1009 | pos = strchr(mine, '='); |
| 1010 | if (pos == NULL) |
| 1011 | { |
| 1012 | pg_free(mine); |
| 1013 | return; |
| 1014 | } |
| 1015 | *pos++ = '\0'; |
| 1016 | |
| 1017 | /* Build the command, with suitable quoting for everything. */ |
| 1018 | appendPQExpBuffer(buf, "ALTER %s %s " , type, fmtId(name)); |
| 1019 | if (type2 != NULL && name2 != NULL) |
| 1020 | appendPQExpBuffer(buf, "IN %s %s " , type2, fmtId(name2)); |
| 1021 | appendPQExpBuffer(buf, "SET %s TO " , fmtId(mine)); |
| 1022 | |
| 1023 | /* |
| 1024 | * Variables that are marked GUC_LIST_QUOTE were already fully quoted by |
| 1025 | * flatten_set_variable_args() before they were put into the setconfig |
| 1026 | * array. However, because the quoting rules used there aren't exactly |
| 1027 | * like SQL's, we have to break the list value apart and then quote the |
| 1028 | * elements as string literals. (The elements may be double-quoted as-is, |
| 1029 | * but we can't just feed them to the SQL parser; it would do the wrong |
| 1030 | * thing with elements that are zero-length or longer than NAMEDATALEN.) |
| 1031 | * |
| 1032 | * Variables that are not so marked should just be emitted as simple |
| 1033 | * string literals. If the variable is not known to |
| 1034 | * variable_is_guc_list_quote(), we'll do that; this makes it unsafe to |
| 1035 | * use GUC_LIST_QUOTE for extension variables. |
| 1036 | */ |
| 1037 | if (variable_is_guc_list_quote(mine)) |
| 1038 | { |
| 1039 | char **namelist; |
| 1040 | char **nameptr; |
| 1041 | |
| 1042 | /* Parse string into list of identifiers */ |
| 1043 | /* this shouldn't fail really */ |
| 1044 | if (SplitGUCList(pos, ',', &namelist)) |
| 1045 | { |
| 1046 | for (nameptr = namelist; *nameptr; nameptr++) |
| 1047 | { |
| 1048 | if (nameptr != namelist) |
| 1049 | appendPQExpBufferStr(buf, ", " ); |
| 1050 | appendStringLiteralConn(buf, *nameptr, conn); |
| 1051 | } |
| 1052 | } |
| 1053 | pg_free(namelist); |
| 1054 | } |
| 1055 | else |
| 1056 | appendStringLiteralConn(buf, pos, conn); |
| 1057 | |
| 1058 | appendPQExpBufferStr(buf, ";\n" ); |
| 1059 | |
| 1060 | pg_free(mine); |
| 1061 | } |
| 1062 | |