mirror of https://github.com/postgres/postgres
Recent commits have removed the various uses it was supporting. It was a performance bottleneck, according to bug report #4919 by Lauris Ulmanis; seems it slowed down user creation after a billion users.REL8_5_ALPHA2_BRANCH
parent
0905e8aeeb
commit
a8bb8eb583
@ -1,931 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* flatfiles.c |
||||
* Routines for maintaining "flat file" images of the shared catalogs. |
||||
* |
||||
* We use flat files so that the postmaster and not-yet-fully-started |
||||
* backends can look at the contents of pg_database, pg_authid, and |
||||
* pg_auth_members for authentication purposes. This module is |
||||
* responsible for keeping the flat-file images as nearly in sync with |
||||
* database reality as possible. |
||||
* |
||||
* The tricky part of the write_xxx_file() routines in this module is that |
||||
* they need to be able to operate in the context of the database startup |
||||
* process (which calls BuildFlatFiles()) as well as a normal backend. |
||||
* This means for example that we can't assume a fully functional relcache |
||||
* and we can't use syscaches at all. The major restriction imposed by |
||||
* all that is that there's no way to read an out-of-line-toasted datum, |
||||
* because the tuptoaster.c code is not prepared to cope with such an |
||||
* environment. Fortunately we can design the shared catalogs in such |
||||
* a way that this is OK. |
||||
* |
||||
* |
||||
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.39 2009/08/31 02:23:22 tgl Exp $ |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include <sys/stat.h> |
||||
#include <unistd.h> |
||||
|
||||
#include "access/heapam.h" |
||||
#include "access/transam.h" |
||||
#include "access/twophase_rmgr.h" |
||||
#include "access/xact.h" |
||||
#include "access/xlogutils.h" |
||||
#include "catalog/pg_auth_members.h" |
||||
#include "catalog/pg_authid.h" |
||||
#include "catalog/pg_database.h" |
||||
#include "catalog/pg_namespace.h" |
||||
#include "catalog/pg_tablespace.h" |
||||
#include "commands/trigger.h" |
||||
#include "miscadmin.h" |
||||
#include "storage/bufmgr.h" |
||||
#include "storage/fd.h" |
||||
#include "storage/lmgr.h" |
||||
#include "storage/pmsignal.h" |
||||
#include "utils/builtins.h" |
||||
#include "utils/flatfiles.h" |
||||
#include "utils/resowner.h" |
||||
#include "utils/tqual.h" |
||||
|
||||
|
||||
/* Actual names of the flat files (within $PGDATA) */ |
||||
#define DATABASE_FLAT_FILE "global/pg_database" |
||||
#define AUTH_FLAT_FILE "global/pg_auth" |
||||
|
||||
/* Info bits in a flatfiles 2PC record */ |
||||
#define FF_BIT_DATABASE 1 |
||||
#define FF_BIT_AUTH 2 |
||||
|
||||
|
||||
/*
|
||||
* The need-to-update-files flags are SubTransactionIds that show |
||||
* what level of the subtransaction tree requested the update. To register |
||||
* an update, the subtransaction saves its own SubTransactionId in the flag, |
||||
* unless the value was already set to a valid SubTransactionId (which implies |
||||
* that it or a parent level has already requested the same). If it aborts |
||||
* and the value is its SubTransactionId, it resets the flag to |
||||
* InvalidSubTransactionId. If it commits, it changes the value to its |
||||
* parent's SubTransactionId. This way the value is propagated up to the |
||||
* top-level transaction, which will update the files if a valid |
||||
* SubTransactionId is seen at top-level commit. |
||||
*/ |
||||
static SubTransactionId database_file_update_subid = InvalidSubTransactionId; |
||||
static SubTransactionId auth_file_update_subid = InvalidSubTransactionId; |
||||
|
||||
|
||||
/*
|
||||
* Mark flat database file as needing an update (because pg_database changed) |
||||
*/ |
||||
void |
||||
database_file_update_needed(void) |
||||
{ |
||||
if (database_file_update_subid == InvalidSubTransactionId) |
||||
database_file_update_subid = GetCurrentSubTransactionId(); |
||||
} |
||||
|
||||
/*
|
||||
* Mark flat auth file as needing an update (because pg_authid or |
||||
* pg_auth_members changed) |
||||
*/ |
||||
void |
||||
auth_file_update_needed(void) |
||||
{ |
||||
if (auth_file_update_subid == InvalidSubTransactionId) |
||||
auth_file_update_subid = GetCurrentSubTransactionId(); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* database_getflatfilename --- get pathname of database file |
||||
* |
||||
* Note that result string is palloc'd, and should be freed by the caller. |
||||
* (This convention is not really needed anymore, since the relative path |
||||
* is fixed.) |
||||
*/ |
||||
char * |
||||
database_getflatfilename(void) |
||||
{ |
||||
return pstrdup(DATABASE_FLAT_FILE); |
||||
} |
||||
|
||||
/*
|
||||
* auth_getflatfilename --- get pathname of auth file |
||||
* |
||||
* Note that result string is palloc'd, and should be freed by the caller. |
||||
* (This convention is not really needed anymore, since the relative path |
||||
* is fixed.) |
||||
*/ |
||||
char * |
||||
auth_getflatfilename(void) |
||||
{ |
||||
return pstrdup(AUTH_FLAT_FILE); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* fputs_quote |
||||
* |
||||
* Outputs string in quotes, with double-quotes duplicated. |
||||
* We could use quote_ident(), but that expects a TEXT argument. |
||||
*/ |
||||
static void |
||||
fputs_quote(const char *str, FILE *fp) |
||||
{ |
||||
fputc('"', fp); |
||||
while (*str) |
||||
{ |
||||
fputc(*str, fp); |
||||
if (*str == '"') |
||||
fputc('"', fp); |
||||
str++; |
||||
} |
||||
fputc('"', fp); |
||||
} |
||||
|
||||
/*
|
||||
* name_okay |
||||
* |
||||
* We must disallow newlines in role names because |
||||
* hba.c's parser won't handle fields split across lines, even if quoted. |
||||
*/ |
||||
static bool |
||||
name_okay(const char *str) |
||||
{ |
||||
int i; |
||||
|
||||
i = strcspn(str, "\r\n"); |
||||
return (str[i] == '\0'); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* write_database_file: update the flat database file |
||||
*/ |
||||
static void |
||||
write_database_file(Relation drel) |
||||
{ |
||||
char *filename, |
||||
*tempname; |
||||
int bufsize; |
||||
FILE *fp; |
||||
mode_t oumask; |
||||
HeapScanDesc scan; |
||||
HeapTuple tuple; |
||||
|
||||
/*
|
||||
* Create a temporary filename to be renamed later. This prevents the |
||||
* backend from clobbering the flat file while the postmaster might be |
||||
* reading from it. |
||||
*/ |
||||
filename = database_getflatfilename(); |
||||
bufsize = strlen(filename) + 12; |
||||
tempname = (char *) palloc(bufsize); |
||||
snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); |
||||
|
||||
oumask = umask((mode_t) 077); |
||||
fp = AllocateFile(tempname, "w"); |
||||
umask(oumask); |
||||
if (fp == NULL) |
||||
ereport(ERROR, |
||||
(errcode_for_file_access(), |
||||
errmsg("could not write to temporary file \"%s\": %m", |
||||
tempname))); |
||||
|
||||
/*
|
||||
* Read pg_database and write the file. |
||||
*/ |
||||
scan = heap_beginscan(drel, SnapshotNow, 0, NULL); |
||||
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) |
||||
{ |
||||
Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple); |
||||
char *datname; |
||||
Oid datoid; |
||||
Oid dattablespace; |
||||
TransactionId datfrozenxid; |
||||
|
||||
datname = NameStr(dbform->datname); |
||||
datoid = HeapTupleGetOid(tuple); |
||||
dattablespace = dbform->dattablespace; |
||||
datfrozenxid = dbform->datfrozenxid; |
||||
|
||||
/*
|
||||
* Check for illegal characters in the database name. |
||||
*/ |
||||
if (!name_okay(datname)) |
||||
{ |
||||
ereport(LOG, |
||||
(errmsg("invalid database name \"%s\"", datname))); |
||||
continue; |
||||
} |
||||
|
||||
/*
|
||||
* The file format is: "dbname" oid tablespace frozenxid |
||||
* |
||||
* The xids are not needed for backend startup, but are of use to |
||||
* autovacuum, and might also be helpful for forensic purposes. |
||||
*/ |
||||
fputs_quote(datname, fp); |
||||
fprintf(fp, " %u %u %u\n", |
||||
datoid, dattablespace, datfrozenxid); |
||||
} |
||||
heap_endscan(scan); |
||||
|
||||
if (FreeFile(fp)) |
||||
ereport(ERROR, |
||||
(errcode_for_file_access(), |
||||
errmsg("could not write to temporary file \"%s\": %m", |
||||
tempname))); |
||||
|
||||
/*
|
||||
* Rename the temp file to its final name, deleting the old flat file. We |
||||
* expect that rename(2) is an atomic action. |
||||
*/ |
||||
if (rename(tempname, filename)) |
||||
ereport(ERROR, |
||||
(errcode_for_file_access(), |
||||
errmsg("could not rename file \"%s\" to \"%s\": %m", |
||||
tempname, filename))); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* Support for write_auth_file |
||||
* |
||||
* The format for the flat auth file is |
||||
* "rolename" "password" "validuntil" "memberof" "memberof" ... |
||||
* Each role's line lists all the roles (groups) of which it is directly |
||||
* or indirectly a member, except for itself. |
||||
* |
||||
* The postmaster expects the file to be sorted by rolename. There is not |
||||
* any special ordering of the membership lists. |
||||
* |
||||
* To construct this information, we scan pg_authid and pg_auth_members, |
||||
* and build data structures in-memory before writing the file. |
||||
*/ |
||||
|
||||
typedef struct |
||||
{ |
||||
Oid roleid; |
||||
char *rolname; |
||||
char *rolpassword; |
||||
char *rolvaliduntil; |
||||
List *member_of; |
||||
} auth_entry; |
||||
|
||||
typedef struct |
||||
{ |
||||
Oid roleid; |
||||
Oid memberid; |
||||
} authmem_entry; |
||||
|
||||
|
||||
/* qsort comparator for sorting auth_entry array by roleid */ |
||||
static int |
||||
oid_compar(const void *a, const void *b) |
||||
{ |
||||
const auth_entry *a_auth = (const auth_entry *) a; |
||||
const auth_entry *b_auth = (const auth_entry *) b; |
||||
|
||||
if (a_auth->roleid < b_auth->roleid) |
||||
return -1; |
||||
if (a_auth->roleid > b_auth->roleid) |
||||
return 1; |
||||
return 0; |
||||
} |
||||
|
||||
/* qsort comparator for sorting auth_entry array by rolname */ |
||||
static int |
||||
name_compar(const void *a, const void *b) |
||||
{ |
||||
const auth_entry *a_auth = (const auth_entry *) a; |
||||
const auth_entry *b_auth = (const auth_entry *) b; |
||||
|
||||
return strcmp(a_auth->rolname, b_auth->rolname); |
||||
} |
||||
|
||||
/* qsort comparator for sorting authmem_entry array by memberid */ |
||||
static int |
||||
mem_compar(const void *a, const void *b) |
||||
{ |
||||
const authmem_entry *a_auth = (const authmem_entry *) a; |
||||
const authmem_entry *b_auth = (const authmem_entry *) b; |
||||
|
||||
if (a_auth->memberid < b_auth->memberid) |
||||
return -1; |
||||
if (a_auth->memberid > b_auth->memberid) |
||||
return 1; |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* write_auth_file: update the flat auth file |
||||
*/ |
||||
static void |
||||
write_auth_file(Relation rel_authid, Relation rel_authmem) |
||||
{ |
||||
char *filename, |
||||
*tempname; |
||||
int bufsize; |
||||
BlockNumber totalblocks; |
||||
FILE *fp; |
||||
mode_t oumask; |
||||
HeapScanDesc scan; |
||||
HeapTuple tuple; |
||||
int curr_role = 0; |
||||
int total_roles = 0; |
||||
int curr_mem = 0; |
||||
int total_mem = 0; |
||||
int est_rows; |
||||
auth_entry *auth_info; |
||||
authmem_entry *authmem_info; |
||||
|
||||
/*
|
||||
* Create a temporary filename to be renamed later. This prevents the |
||||
* backend from clobbering the flat file while the postmaster might be |
||||
* reading from it. |
||||
*/ |
||||
filename = auth_getflatfilename(); |
||||
bufsize = strlen(filename) + 12; |
||||
tempname = (char *) palloc(bufsize); |
||||
snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); |
||||
|
||||
oumask = umask((mode_t) 077); |
||||
fp = AllocateFile(tempname, "w"); |
||||
umask(oumask); |
||||
if (fp == NULL) |
||||
ereport(ERROR, |
||||
(errcode_for_file_access(), |
||||
errmsg("could not write to temporary file \"%s\": %m", |
||||
tempname))); |
||||
|
||||
/*
|
||||
* Read pg_authid and fill temporary data structures. |
||||
*/ |
||||
totalblocks = RelationGetNumberOfBlocks(rel_authid); |
||||
totalblocks = totalblocks ? totalblocks : 1; |
||||
est_rows = totalblocks * (BLCKSZ / (sizeof(HeapTupleHeaderData) + sizeof(FormData_pg_authid))); |
||||
auth_info = (auth_entry *) palloc(est_rows * sizeof(auth_entry)); |
||||
|
||||
scan = heap_beginscan(rel_authid, SnapshotNow, 0, NULL); |
||||
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) |
||||
{ |
||||
Form_pg_authid aform = (Form_pg_authid) GETSTRUCT(tuple); |
||||
HeapTupleHeader tup = tuple->t_data; |
||||
char *tp; /* ptr to tuple data */ |
||||
long off; /* offset in tuple data */ |
||||
bits8 *bp = tup->t_bits; /* ptr to null bitmask in tuple */ |
||||
Datum datum; |
||||
|
||||
if (curr_role >= est_rows) |
||||
{ |
||||
est_rows *= 2; |
||||
auth_info = (auth_entry *) |
||||
repalloc(auth_info, est_rows * sizeof(auth_entry)); |
||||
} |
||||
|
||||
auth_info[curr_role].roleid = HeapTupleGetOid(tuple); |
||||
auth_info[curr_role].rolname = pstrdup(NameStr(aform->rolname)); |
||||
auth_info[curr_role].member_of = NIL; |
||||
|
||||
/*
|
||||
* We can't use heap_getattr() here because during startup we will not |
||||
* have any tupdesc for pg_authid. Fortunately it's not too hard to |
||||
* work around this. rolpassword is the first possibly-null field so |
||||
* we can compute its offset directly. Note that this only works |
||||
* reliably because the preceding field (rolconnlimit) is int4, and |
||||
* therefore rolpassword is always 4-byte-aligned, and will be at the |
||||
* same offset no matter whether it uses 1-byte or 4-byte header. |
||||
*/ |
||||
tp = (char *) tup + tup->t_hoff; |
||||
off = offsetof(FormData_pg_authid, rolpassword); |
||||
|
||||
if (HeapTupleHasNulls(tuple) && |
||||
att_isnull(Anum_pg_authid_rolpassword - 1, bp)) |
||||
{ |
||||
/* passwd is null, emit as an empty string */ |
||||
auth_info[curr_role].rolpassword = pstrdup(""); |
||||
} |
||||
else |
||||
{ |
||||
/* assume passwd is pass-by-ref */ |
||||
datum = PointerGetDatum(tp + off); |
||||
|
||||
/*
|
||||
* The password probably shouldn't ever be out-of-line toasted; if |
||||
* it is, ignore it, since we can't handle that in startup mode. |
||||
* |
||||
* It is entirely likely that it's 1-byte format not 4-byte, and |
||||
* theoretically possible that it's compressed inline, but |
||||
* text_to_cstring should be able to handle those cases even in |
||||
* startup mode. |
||||
*/ |
||||
if (VARATT_IS_EXTERNAL(DatumGetPointer(datum))) |
||||
auth_info[curr_role].rolpassword = pstrdup(""); |
||||
else |
||||
auth_info[curr_role].rolpassword = TextDatumGetCString(datum); |
||||
|
||||
/* assume passwd has attlen -1 */ |
||||
off = att_addlength_pointer(off, -1, tp + off); |
||||
} |
||||
|
||||
if (HeapTupleHasNulls(tuple) && |
||||
att_isnull(Anum_pg_authid_rolvaliduntil - 1, bp)) |
||||
{ |
||||
/* rolvaliduntil is null, emit as an empty string */ |
||||
auth_info[curr_role].rolvaliduntil = pstrdup(""); |
||||
} |
||||
else |
||||
{ |
||||
TimestampTz *rvup; |
||||
|
||||
/* Assume timestamptz has double alignment */ |
||||
off = att_align_nominal(off, 'd'); |
||||
rvup = (TimestampTz *) (tp + off); |
||||
auth_info[curr_role].rolvaliduntil = |
||||
DatumGetCString(DirectFunctionCall1(timestamptz_out, |
||||
TimestampTzGetDatum(*rvup))); |
||||
} |
||||
|
||||
/*
|
||||
* Check for illegal characters in the user name and password. |
||||
*/ |
||||
if (!name_okay(auth_info[curr_role].rolname)) |
||||
{ |
||||
ereport(LOG, |
||||
(errmsg("invalid role name \"%s\"", |
||||
auth_info[curr_role].rolname))); |
||||
continue; |
||||
} |
||||
if (!name_okay(auth_info[curr_role].rolpassword)) |
||||
{ |
||||
ereport(LOG, |
||||
(errmsg("invalid role password \"%s\"", |
||||
auth_info[curr_role].rolpassword))); |
||||
continue; |
||||
} |
||||
|
||||
curr_role++; |
||||
total_roles++; |
||||
} |
||||
heap_endscan(scan); |
||||
|
||||
/*
|
||||
* Read pg_auth_members into temporary data structure, too |
||||
*/ |
||||
totalblocks = RelationGetNumberOfBlocks(rel_authmem); |
||||
totalblocks = totalblocks ? totalblocks : 1; |
||||
est_rows = totalblocks * (BLCKSZ / (sizeof(HeapTupleHeaderData) + sizeof(FormData_pg_auth_members))); |
||||
authmem_info = (authmem_entry *) palloc(est_rows * sizeof(authmem_entry)); |
||||
|
||||
scan = heap_beginscan(rel_authmem, SnapshotNow, 0, NULL); |
||||
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) |
||||
{ |
||||
Form_pg_auth_members memform = (Form_pg_auth_members) GETSTRUCT(tuple); |
||||
|
||||
if (curr_mem >= est_rows) |
||||
{ |
||||
est_rows *= 2; |
||||
authmem_info = (authmem_entry *) |
||||
repalloc(authmem_info, est_rows * sizeof(authmem_entry)); |
||||
} |
||||
|
||||
authmem_info[curr_mem].roleid = memform->roleid; |
||||
authmem_info[curr_mem].memberid = memform->member; |
||||
curr_mem++; |
||||
total_mem++; |
||||
} |
||||
heap_endscan(scan); |
||||
|
||||
/*
|
||||
* Search for memberships. We can skip all this if pg_auth_members is |
||||
* empty. |
||||
*/ |
||||
if (total_mem > 0) |
||||
{ |
||||
/*
|
||||
* Sort auth_info by roleid and authmem_info by memberid. |
||||
*/ |
||||
qsort(auth_info, total_roles, sizeof(auth_entry), oid_compar); |
||||
qsort(authmem_info, total_mem, sizeof(authmem_entry), mem_compar); |
||||
|
||||
/*
|
||||
* For each role, find what it belongs to. |
||||
*/ |
||||
for (curr_role = 0; curr_role < total_roles; curr_role++) |
||||
{ |
||||
List *roles_list; |
||||
List *roles_names_list = NIL; |
||||
ListCell *mem; |
||||
|
||||
/*
|
||||
* This search algorithm is the same as in is_member_of_role; we |
||||
* are just working with a different input data structure. |
||||
*/ |
||||
roles_list = list_make1_oid(auth_info[curr_role].roleid); |
||||
|
||||
foreach(mem, roles_list) |
||||
{ |
||||
authmem_entry key; |
||||
authmem_entry *found_mem; |
||||
int first_found, |
||||
last_found, |
||||
i; |
||||
|
||||
key.memberid = lfirst_oid(mem); |
||||
found_mem = bsearch(&key, authmem_info, total_mem, |
||||
sizeof(authmem_entry), mem_compar); |
||||
if (!found_mem) |
||||
continue; |
||||
|
||||
/*
|
||||
* bsearch found a match for us; but if there were multiple |
||||
* matches it could have found any one of them. Locate first |
||||
* and last match. |
||||
*/ |
||||
first_found = last_found = (found_mem - authmem_info); |
||||
while (first_found > 0 && |
||||
mem_compar(&key, &authmem_info[first_found - 1]) == 0) |
||||
first_found--; |
||||
while (last_found + 1 < total_mem && |
||||
mem_compar(&key, &authmem_info[last_found + 1]) == 0) |
||||
last_found++; |
||||
|
||||
/*
|
||||
* Now add all the new roles to roles_list. |
||||
*/ |
||||
for (i = first_found; i <= last_found; i++) |
||||
roles_list = list_append_unique_oid(roles_list, |
||||
authmem_info[i].roleid); |
||||
} |
||||
|
||||
/*
|
||||
* Convert list of role Oids to list of role names. We must do |
||||
* this before re-sorting auth_info. |
||||
* |
||||
* We skip the first list element (curr_role itself) since there |
||||
* is no point in writing that a role is a member of itself. |
||||
*/ |
||||
for_each_cell(mem, lnext(list_head(roles_list))) |
||||
{ |
||||
auth_entry key_auth; |
||||
auth_entry *found_role; |
||||
|
||||
key_auth.roleid = lfirst_oid(mem); |
||||
found_role = bsearch(&key_auth, auth_info, total_roles, |
||||
sizeof(auth_entry), oid_compar); |
||||
if (found_role) /* paranoia */ |
||||
roles_names_list = lappend(roles_names_list, |
||||
found_role->rolname); |
||||
} |
||||
auth_info[curr_role].member_of = roles_names_list; |
||||
list_free(roles_list); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Now sort auth_info into rolname order for output, and write the file. |
||||
*/ |
||||
qsort(auth_info, total_roles, sizeof(auth_entry), name_compar); |
||||
|
||||
for (curr_role = 0; curr_role < total_roles; curr_role++) |
||||
{ |
||||
auth_entry *arole = &auth_info[curr_role]; |
||||
ListCell *mem; |
||||
|
||||
fputs_quote(arole->rolname, fp); |
||||
fputs(" ", fp); |
||||
fputs_quote(arole->rolpassword, fp); |
||||
fputs(" ", fp); |
||||
fputs_quote(arole->rolvaliduntil, fp); |
||||
|
||||
foreach(mem, arole->member_of) |
||||
{ |
||||
fputs(" ", fp); |
||||
fputs_quote((char *) lfirst(mem), fp); |
||||
} |
||||
|
||||
fputs("\n", fp); |
||||
} |
||||
|
||||
if (FreeFile(fp)) |
||||
ereport(ERROR, |
||||
(errcode_for_file_access(), |
||||
errmsg("could not write to temporary file \"%s\": %m", |
||||
tempname))); |
||||
|
||||
/*
|
||||
* Rename the temp file to its final name, deleting the old flat file. We |
||||
* expect that rename(2) is an atomic action. |
||||
*/ |
||||
if (rename(tempname, filename)) |
||||
ereport(ERROR, |
||||
(errcode_for_file_access(), |
||||
errmsg("could not rename file \"%s\" to \"%s\": %m", |
||||
tempname, filename))); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* This routine is called once during database startup, after completing |
||||
* WAL replay if needed. Its purpose is to sync the flat files with the |
||||
* current state of the database tables. This is particularly important |
||||
* during PITR operation, since the flat files will come from the |
||||
* base backup which may be far out of sync with the current state. |
||||
* |
||||
* In theory we could skip rebuilding the flat files if no WAL replay |
||||
* occurred, but it seems best to just do it always. We have to |
||||
* scan pg_database to compute the XID wrap limit anyway. Also, this |
||||
* policy means we need not force initdb to change the format of the |
||||
* flat files. |
||||
* |
||||
* In a standalone backend we pass database_only = true to skip processing |
||||
* the auth file. We won't need it, and building it could fail if there's |
||||
* something corrupt in the authid/authmem catalogs. |
||||
*/ |
||||
void |
||||
BuildFlatFiles(bool database_only) |
||||
{ |
||||
ResourceOwner owner; |
||||
RelFileNode rnode; |
||||
Relation rel_db, |
||||
rel_authid, |
||||
rel_authmem; |
||||
|
||||
/* Need a resowner to keep the heapam and buffer code happy */ |
||||
owner = ResourceOwnerCreate(NULL, "BuildFlatFiles"); |
||||
CurrentResourceOwner = owner; |
||||
|
||||
/* hard-wired path to pg_database */ |
||||
rnode.spcNode = GLOBALTABLESPACE_OID; |
||||
rnode.dbNode = 0; |
||||
rnode.relNode = DatabaseRelationId; |
||||
|
||||
/*
|
||||
* We don't have any hope of running a real relcache, but we can use the |
||||
* same fake-relcache facility that WAL replay uses. |
||||
* |
||||
* No locking is needed because no one else is alive yet. |
||||
*/ |
||||
rel_db = CreateFakeRelcacheEntry(rnode); |
||||
write_database_file(rel_db); |
||||
FreeFakeRelcacheEntry(rel_db); |
||||
|
||||
if (!database_only) |
||||
{ |
||||
/* hard-wired path to pg_authid */ |
||||
rnode.spcNode = GLOBALTABLESPACE_OID; |
||||
rnode.dbNode = 0; |
||||
rnode.relNode = AuthIdRelationId; |
||||
rel_authid = CreateFakeRelcacheEntry(rnode); |
||||
|
||||
/* hard-wired path to pg_auth_members */ |
||||
rnode.spcNode = GLOBALTABLESPACE_OID; |
||||
rnode.dbNode = 0; |
||||
rnode.relNode = AuthMemRelationId; |
||||
rel_authmem = CreateFakeRelcacheEntry(rnode); |
||||
|
||||
write_auth_file(rel_authid, rel_authmem); |
||||
FreeFakeRelcacheEntry(rel_authid); |
||||
FreeFakeRelcacheEntry(rel_authmem); |
||||
} |
||||
|
||||
CurrentResourceOwner = NULL; |
||||
ResourceOwnerDelete(owner); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* This routine is called during transaction commit or abort. |
||||
* |
||||
* On commit, if we've written any of the critical database tables during |
||||
* the current transaction, update the flat files and signal the postmaster. |
||||
* |
||||
* On abort, just reset the static flags so we don't try to do it on the |
||||
* next successful commit. |
||||
* |
||||
* NB: this should be the last step before actual transaction commit. |
||||
* If any error aborts the transaction after we run this code, the postmaster |
||||
* will still have received and cached the changed data; so minimize the |
||||
* window for such problems. |
||||
*/ |
||||
void |
||||
AtEOXact_UpdateFlatFiles(bool isCommit) |
||||
{ |
||||
Relation drel = NULL; |
||||
Relation arel = NULL; |
||||
Relation mrel = NULL; |
||||
|
||||
if (database_file_update_subid == InvalidSubTransactionId && |
||||
auth_file_update_subid == InvalidSubTransactionId) |
||||
return; /* nothing to do */ |
||||
|
||||
if (!isCommit) |
||||
{ |
||||
database_file_update_subid = InvalidSubTransactionId; |
||||
auth_file_update_subid = InvalidSubTransactionId; |
||||
return; |
||||
} |
||||
|
||||
/*
|
||||
* Advance command counter to be certain we see all effects of the current |
||||
* transaction. |
||||
*/ |
||||
CommandCounterIncrement(); |
||||
|
||||
/*
|
||||
* Open and lock the needed catalog(s). |
||||
* |
||||
* Even though we only need AccessShareLock, this could theoretically fail |
||||
* due to deadlock. In practice, however, our transaction already holds |
||||
* RowExclusiveLock or better (it couldn't have updated the catalog |
||||
* without such a lock). This implies that dbcommands.c and other places |
||||
* that force flat-file updates must not follow the common practice of |
||||
* dropping catalog locks before commit. |
||||
*/ |
||||
if (database_file_update_subid != InvalidSubTransactionId) |
||||
drel = heap_open(DatabaseRelationId, AccessShareLock); |
||||
|
||||
if (auth_file_update_subid != InvalidSubTransactionId) |
||||
{ |
||||
arel = heap_open(AuthIdRelationId, AccessShareLock); |
||||
mrel = heap_open(AuthMemRelationId, AccessShareLock); |
||||
} |
||||
|
||||
/*
|
||||
* Obtain special locks to ensure that two transactions don't try to write |
||||
* the same flat file concurrently. Quite aside from any direct risks of |
||||
* corrupted output, the winning writer probably wouldn't have seen the |
||||
* other writer's updates. By taking a lock and holding it till commit, |
||||
* we ensure that whichever updater goes second will see the other |
||||
* updater's changes as committed, and thus the final state of the file |
||||
* will include all updates. |
||||
* |
||||
* We use a lock on "database 0" to protect writing the pg_database flat |
||||
* file, and a lock on "role 0" to protect the auth file. This is a bit |
||||
* ugly but it's not worth inventing any more-general convention. (Any |
||||
* two locktags that are never used for anything else would do.) |
||||
* |
||||
* This is safe against deadlock as long as these are the very last locks |
||||
* acquired during the transaction. |
||||
*/ |
||||
if (database_file_update_subid != InvalidSubTransactionId) |
||||
LockSharedObject(DatabaseRelationId, InvalidOid, 0, |
||||
AccessExclusiveLock); |
||||
|
||||
if (auth_file_update_subid != InvalidSubTransactionId) |
||||
LockSharedObject(AuthIdRelationId, InvalidOid, 0, |
||||
AccessExclusiveLock); |
||||
|
||||
/* Okay to write the files */ |
||||
if (database_file_update_subid != InvalidSubTransactionId) |
||||
{ |
||||
database_file_update_subid = InvalidSubTransactionId; |
||||
write_database_file(drel); |
||||
heap_close(drel, NoLock); |
||||
} |
||||
|
||||
if (auth_file_update_subid != InvalidSubTransactionId) |
||||
{ |
||||
auth_file_update_subid = InvalidSubTransactionId; |
||||
write_auth_file(arel, mrel); |
||||
heap_close(arel, NoLock); |
||||
heap_close(mrel, NoLock); |
||||
} |
||||
|
||||
/*
|
||||
* Force synchronous commit, to minimize the window between changing the |
||||
* flat files on-disk and marking the transaction committed. It's not |
||||
* great that there is any window at all, but definitely we don't want to |
||||
* make it larger than necessary. |
||||
*/ |
||||
ForceSyncCommit(); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* This routine is called during transaction prepare. |
||||
* |
||||
* Record which files need to be refreshed if this transaction later |
||||
* commits. |
||||
* |
||||
* Note: it's OK to clear the flags immediately, since if the PREPARE fails |
||||
* further on, we'd only reset the flags anyway. So there's no need for a |
||||
* separate PostPrepare call. |
||||
*/ |
||||
void |
||||
AtPrepare_UpdateFlatFiles(void) |
||||
{ |
||||
uint16 info = 0; |
||||
|
||||
if (database_file_update_subid != InvalidSubTransactionId) |
||||
{ |
||||
database_file_update_subid = InvalidSubTransactionId; |
||||
info |= FF_BIT_DATABASE; |
||||
} |
||||
if (auth_file_update_subid != InvalidSubTransactionId) |
||||
{ |
||||
auth_file_update_subid = InvalidSubTransactionId; |
||||
info |= FF_BIT_AUTH; |
||||
} |
||||
if (info != 0) |
||||
RegisterTwoPhaseRecord(TWOPHASE_RM_FLATFILES_ID, info, |
||||
NULL, 0); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* AtEOSubXact_UpdateFlatFiles |
||||
* |
||||
* Called at subtransaction end, this routine resets or updates the |
||||
* need-to-update-files flags. |
||||
*/ |
||||
void |
||||
AtEOSubXact_UpdateFlatFiles(bool isCommit, |
||||
SubTransactionId mySubid, |
||||
SubTransactionId parentSubid) |
||||
{ |
||||
if (isCommit) |
||||
{ |
||||
if (database_file_update_subid == mySubid) |
||||
database_file_update_subid = parentSubid; |
||||
|
||||
if (auth_file_update_subid == mySubid) |
||||
auth_file_update_subid = parentSubid; |
||||
} |
||||
else |
||||
{ |
||||
if (database_file_update_subid == mySubid) |
||||
database_file_update_subid = InvalidSubTransactionId; |
||||
|
||||
if (auth_file_update_subid == mySubid) |
||||
auth_file_update_subid = InvalidSubTransactionId; |
||||
} |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* This trigger is fired whenever someone modifies pg_database, pg_authid |
||||
* or pg_auth_members via general-purpose INSERT/UPDATE/DELETE commands. |
||||
* |
||||
* It is sufficient for this to be a STATEMENT trigger since we don't |
||||
* care which individual rows changed. It doesn't much matter whether |
||||
* it's a BEFORE or AFTER trigger. |
||||
*/ |
||||
Datum |
||||
flatfile_update_trigger(PG_FUNCTION_ARGS) |
||||
{ |
||||
TriggerData *trigdata = (TriggerData *) fcinfo->context; |
||||
|
||||
if (!CALLED_AS_TRIGGER(fcinfo)) |
||||
elog(ERROR, |
||||
"flatfile_update_trigger was not called by trigger manager"); |
||||
|
||||
if (RelationGetNamespace(trigdata->tg_relation) != PG_CATALOG_NAMESPACE) |
||||
elog(ERROR, "flatfile_update_trigger was called for wrong table"); |
||||
|
||||
switch (RelationGetRelid(trigdata->tg_relation)) |
||||
{ |
||||
case DatabaseRelationId: |
||||
database_file_update_needed(); |
||||
break; |
||||
case AuthIdRelationId: |
||||
case AuthMemRelationId: |
||||
auth_file_update_needed(); |
||||
break; |
||||
default: |
||||
elog(ERROR, "flatfile_update_trigger was called for wrong table"); |
||||
break; |
||||
} |
||||
|
||||
return PointerGetDatum(NULL); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* 2PC processing routine for COMMIT PREPARED case. |
||||
* |
||||
* (We don't have to do anything for ROLLBACK PREPARED.) |
||||
*/ |
||||
void |
||||
flatfile_twophase_postcommit(TransactionId xid, uint16 info, |
||||
void *recdata, uint32 len) |
||||
{ |
||||
/*
|
||||
* Set flags to do the needed file updates at the end of my own current |
||||
* transaction. (XXX this has some issues if my own transaction later |
||||
* rolls back, or if there is any significant delay before I commit. OK |
||||
* for now because we disallow COMMIT PREPARED inside a transaction |
||||
* block.) |
||||
*/ |
||||
if (info & FF_BIT_DATABASE) |
||||
database_file_update_needed(); |
||||
if (info & FF_BIT_AUTH) |
||||
auth_file_update_needed(); |
||||
} |
||||
@ -1,35 +0,0 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* flatfiles.h |
||||
* Routines for maintaining "flat file" images of the shared catalogs. |
||||
* |
||||
* |
||||
* $PostgreSQL: pgsql/src/include/utils/flatfiles.h,v 1.6 2005/10/15 02:49:46 momjian Exp $ |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef FLATFILES_H |
||||
#define FLATFILES_H |
||||
|
||||
#include "fmgr.h" |
||||
|
||||
extern void database_file_update_needed(void); |
||||
extern void auth_file_update_needed(void); |
||||
|
||||
extern char *database_getflatfilename(void); |
||||
extern char *auth_getflatfilename(void); |
||||
|
||||
extern void BuildFlatFiles(bool database_only); |
||||
|
||||
extern void AtPrepare_UpdateFlatFiles(void); |
||||
extern void AtEOXact_UpdateFlatFiles(bool isCommit); |
||||
extern void AtEOSubXact_UpdateFlatFiles(bool isCommit, |
||||
SubTransactionId mySubid, |
||||
SubTransactionId parentSubid); |
||||
|
||||
extern Datum flatfile_update_trigger(PG_FUNCTION_ARGS); |
||||
|
||||
extern void flatfile_twophase_postcommit(TransactionId xid, uint16 info, |
||||
void *recdata, uint32 len); |
||||
|
||||
#endif /* FLATFILES_H */ |
||||
Loading…
Reference in new issue