blob: 79f5ec31d095a05f74be522c58286787cfd610ea [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* 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-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.23 2006/11/05 23:40:31 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "access/heapam.h"
#include "access/transam.h"
#include "access/twophase_rmgr.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_auth_time_constraint.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_filesystem.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "storage/pmsignal.h"
#include "utils/builtins.h"
#include "utils/flatfiles.h"
#include "utils/guc.h"
#include "utils/relcache.h"
#include "utils/resowner.h"
#include "utils/syscache.h"
#include "cdb/cdbmirroredflatfile.h"
/* Actual names of the flat files (within $PGDATA) */
#define DATABASE_FLAT_FILE "global/pg_database"
#define AUTH_FLAT_FILE "global/pg_auth"
#define AUTH_TIME_FLAT_FILE "global/pg_auth_time_constraint"
#define FILESYSTEM_FLAT_FILE "global/pg_filesystem"
/* Info bits in a flatfiles 2PC record */
#define FF_BIT_DATABASE 1
#define FF_BIT_AUTH 2
#define FF_BIT_AUTH_TIME 4
#define FF_BIT_FILESYSTEM 8
/*
* 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;
static SubTransactionId auth_time_file_update_subid = InvalidSubTransactionId;
static SubTransactionId filesystem_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();
}
/*
* Mark flat auth time file as needing an update (because
* pg_auth_time_constraint changed)
*/
void
auth_time_file_update_needed(void)
{
if (auth_time_file_update_subid == InvalidSubTransactionId)
auth_time_file_update_subid = GetCurrentSubTransactionId();
}
/*
* Mark flat filesystem file as needing an update (because
* pg_filesystem changed)
*/
void
filesystem_file_update_needed(void)
{
if (filesystem_file_update_subid == InvalidSubTransactionId)
filesystem_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);
}
/*
* auth_time_getflatfilename --- get pathname of auth_time_constraint 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_time_getflatfilename(void)
{
return pstrdup(AUTH_TIME_FLAT_FILE);
}
/*
* filesystem_getflatfilename --- get pathname of filesystem 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 *
filesystem_getflatfilename(void)
{
return pstrdup(FILESYSTEM_FLAT_FILE);
}
/*
* puts_quote
*
* Copies str info buffer, quoting it and duplicating quote characters
* to escape them.
*/
static void
sputs_quote(StringInfo buffer, const char *str)
{
appendStringInfoChar(buffer, '"');
while (*str)
{
appendStringInfoChar(buffer, *str);
if (*str == '"')
appendStringInfoChar(buffer, '"');
str++;
}
appendStringInfoChar(buffer, '"');
}
/*
* 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
*
* A side effect is to determine the oldest database's datfrozenxid
* so we can set or update the XID wrap limit.
*
* Also, if "startup" is true, we tell relcache.c to clear out the relcache
* init file in each database. That's a bit nonmodular, but scanning
* pg_database twice during system startup seems too high a price for keeping
* things better separated.
*/
static void
write_database_file(Relation drel, bool startup)
{
StringInfoData buffer;
HeapScanDesc scan;
HeapTuple tuple;
NameData oldest_datname;
TransactionId oldest_datfrozenxid = InvalidTransactionId;
MirroredFlatFileOpen mirroredOpen;
initStringInfo(&buffer);
MirroredFlatFile_Open(
&mirroredOpen,
"global",
"pg_database",
O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY,
S_IRUSR | S_IWUSR,
/* suppressError */ false,
/* atomic operation */ true,
/*isMirrorRecovery */ false);
/*
* 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;
/*
* Identify the oldest datfrozenxid. This must match
* the logic in vac_truncate_clog() in vacuum.c.
*/
if (TransactionIdIsNormal(datfrozenxid))
{
if (oldest_datfrozenxid == InvalidTransactionId ||
TransactionIdPrecedes(datfrozenxid, oldest_datfrozenxid))
{
oldest_datfrozenxid = datfrozenxid;
namestrcpy(&oldest_datname, datname);
}
}
/*
* 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.
*/
sputs_quote(&buffer, datname);
appendStringInfo(&buffer, " %u %u %u\n",
datoid, dattablespace, datfrozenxid);
/*
* MPP-10111 - During database expansion we need to be able to bring a
* database up in order to correct the filespace locations in the
* catalog. At this point we will not be able to resolve database paths
* for databases not stored in "pg_default" or "pg_global".
*
* This is solved by passing a special guc to the startup during this
* phase of expand to bypass logic involving non-system tablespaces.
* Since we are bypassing the clearing of the relation cache on these
* databases we need to ensure that we don't try to use them at all
* elsewhere. This is done with a similar check in
* PersistentTablespace_GetFilespacePath().
*/
if (gp_before_filespace_setup && !IsBuiltinTablespace(dattablespace))
continue;
/*
* Also clear relcache init file for each DB if starting up.
*/
if (startup)
{
char *dbpath = GetDatabasePath(datoid, dattablespace);
RelationCacheInitFileRemove(dbpath);
pfree(dbpath);
}
}
heap_endscan(scan);
MirroredFlatFile_Append(&mirroredOpen, buffer.data, buffer.len,
/* suppressError */ false);
MirroredFlatFile_Flush(&mirroredOpen, /* suppressError */ false);
MirroredFlatFile_Close(&mirroredOpen);
if (buffer.maxlen > 0)
pfree(buffer.data);
/*
* Set the transaction ID wrap limit using the oldest datfrozenxid
*/
if (oldest_datfrozenxid != InvalidTransactionId)
SetTransactionIdLimit(oldest_datfrozenxid, &oldest_datname);
}
/*
* Support for write_auth_file
*
* The format for the flat auth file is
* "rolename" "password" "validuntil" "memberof" "memberof" ...
* Only roles that are marked rolcanlogin are entered into the auth file.
* 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;
bool rolcanlogin;
bool rolsuper;
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;
}
/*
* load_auth_entries: read pg_authid into auth_entry[]
*
* auth_info_out: pointer to auth_entry * where address to auth_entry[] should be stored
* total_roles_out: pointer to int where num of total roles should be stored
*/
static void
load_auth_entries(Relation rel_authid, auth_entry **auth_info_out, int *total_roles_out)
{
BlockNumber totalblocks;
HeapScanDesc scan;
HeapTuple tuple;
int curr_role = 0;
int total_roles = 0;
int est_rows;
auth_entry *auth_info;
/*
* Read pg_authid and fill temporary data structures. Note we must read
* all roles, even those without rolcanlogin.
*/
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].rolsuper = aform->rolsuper;
auth_info[curr_role].rolcanlogin = aform->rolcanlogin;
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.
*/
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.
*/
if (VARATT_IS_EXTERNAL(DatumGetPointer(datum)))
auth_info[curr_role].rolpassword = pstrdup("");
else
auth_info[curr_role].rolpassword = DatumGetCString(DirectFunctionCall1(textout, datum));
/* assume passwd has attlen -1 */
off = att_addlength(off, -1, PointerGetDatum(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
{
/*
* rolvaliduntil is timestamptz, which we assume is double
* alignment and pass-by-value.
*/
off = att_align(off, 'd');
datum = fetch_att(tp + off, true, sizeof(TimestampTz));
auth_info[curr_role].rolvaliduntil = DatumGetCString(DirectFunctionCall1(timestamptz_out, datum));
}
/*
* 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);
*auth_info_out = auth_info;
*total_roles_out = total_roles;
}
/*
* write_auth_file: update the flat auth file
*/
static void
write_auth_file(Relation rel_authid, Relation rel_authmem)
{
StringInfoData buffer;
BlockNumber totalblocks;
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;
MirroredFlatFileOpen mirroredOpen;
initStringInfo(&buffer);
load_auth_entries(rel_authid, &auth_info, &total_roles);
/*
* 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;
/* We can skip this for non-login roles */
if (!auth_info[curr_role].rolcanlogin)
continue;
/*
* 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);
/*
* 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.
*/
MirroredFlatFile_Open(
&mirroredOpen,
"global",
"pg_auth",
O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY,
S_IRUSR | S_IWUSR,
/* suppressError */ false,
/* atomic operation */ true,
/*isMirrorRecovery */ false);
for (curr_role = 0; curr_role < total_roles; curr_role++)
{
auth_entry *arole = &auth_info[curr_role];
if (arole->rolcanlogin)
{
ListCell *mem;
sputs_quote(&buffer, arole->rolname);
appendStringInfoChar(&buffer, ' ');
sputs_quote(&buffer, arole->rolpassword);
appendStringInfoChar(&buffer, ' ');
sputs_quote(&buffer, arole->rolvaliduntil);
foreach(mem, arole->member_of)
{
appendStringInfoChar(&buffer, ' ');
sputs_quote(&buffer, (char *) lfirst(mem));
}
appendStringInfoChar(&buffer, '\n');
}
}
MirroredFlatFile_Append(&mirroredOpen, buffer.data, buffer.len,
/* suppressError */ false);
MirroredFlatFile_Flush(&mirroredOpen, /* suppressError */ false);
MirroredFlatFile_Close(&mirroredOpen);
}
/*
* Support for write_auth_time_file
*
* The format for the flat auth file is
* "rolename" "startday" "starttime" "endday" "endtime"
*
* To construct this information, we scan pg_auth_time_constraint,
* and build data structures in-memory before writing the file.
*/
typedef struct
{
Oid roleid;
char *rolname;
int16 startday;
TimeADT starttime;
int16 endday;
TimeADT endtime;
} authtime_entry;
/* qsort comparator for sorting auth_entry array by roleid */
static int
time_oid_compar(const void *a, const void *b)
{
const authtime_entry *x = (const authtime_entry *) a;
const authtime_entry *y = (const authtime_entry *) b;
if (x->roleid < y->roleid)
return -1;
if (x->roleid > y->roleid)
return 1;
return 0;
}
/* qsort comparator for sorting auth_entry array by rolname */
static int
time_name_compar(const void *a, const void *b)
{
const authtime_entry *x = (const authtime_entry *) a;
const authtime_entry *y = (const authtime_entry *) b;
return strcmp(x->rolname, y->rolname);
}
/*
* write_auth_time_file: update the flat auth time constraint file
*/
static void
write_auth_time_file(Relation rel_authid, Relation rel_authtime)
{
StringInfoData buffer;
BlockNumber totalblocks;
HeapScanDesc scan;
HeapTuple tuple;
int total_roles = 0;
int curr_constraint = 0;
int total_constraints = 0;
int est_rows;
auth_entry *auth_info;
authtime_entry *authtime_info;
MirroredFlatFileOpen mirroredOpen;
initStringInfo(&buffer);
load_auth_entries(rel_authid, &auth_info, &total_roles);
/*
* Read pg_auth_time_constraint into temporary data structure, too
*/
totalblocks = RelationGetNumberOfBlocks(rel_authtime);
totalblocks = totalblocks ? totalblocks : 1;
est_rows = totalblocks * (BLCKSZ / (sizeof(HeapTupleHeaderData) + sizeof(FormData_pg_auth_time_constraint)));
authtime_info = (authtime_entry *) palloc(est_rows * sizeof(authtime_entry));
scan = heap_beginscan(rel_authtime, SnapshotNow, 0, NULL);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
Form_pg_auth_time_constraint timeform = (Form_pg_auth_time_constraint) GETSTRUCT(tuple);
if (curr_constraint >= est_rows)
{
est_rows *= 2;
authtime_info = (authtime_entry *)
repalloc(authtime_info, est_rows * sizeof(authtime_entry));
}
authtime_info[curr_constraint].roleid = timeform->authid;
authtime_info[curr_constraint].startday = timeform->start_day;
authtime_info[curr_constraint].starttime = timeform->start_time;
authtime_info[curr_constraint].endday = timeform->end_day;
authtime_info[curr_constraint].endtime = timeform->end_time;
curr_constraint++;
total_constraints++;
}
heap_endscan(scan);
/*
* Search for rolenames. We can skip all this if pg_auth_time_constraint is
* empty.
*/
if (total_constraints > 0)
{
/*
* Sort auth_info by roleid and authtime_info by roleid.
*/
qsort(auth_info, total_roles, sizeof(auth_entry), oid_compar);
qsort(authtime_info, total_constraints, sizeof(authtime_entry), time_oid_compar);
/*
* For each time constraint, determine the rolename.
* TODO: this is O(nlogm). we could do O(n + m).
*/
for (curr_constraint = 0; curr_constraint < total_constraints; curr_constraint++)
{
auth_entry key_auth;
auth_entry *found_role;
key_auth.roleid = authtime_info[curr_constraint].roleid;
found_role = bsearch(&key_auth, auth_info, total_roles,
sizeof(auth_entry), oid_compar);
if (found_role)
{
/*
* All constraints written out will be enforced.
* Constraints against superusers must not be allowed.
*/
if (found_role->rolsuper)
ereport(WARNING,
(errmsg("time constraints are being added against superuser")));
authtime_info[curr_constraint].rolname = found_role->rolname;
}
}
}
/*
* Now sort auth_info into rolname order for output, and write the file.
*/
qsort(authtime_info, total_constraints, sizeof(authtime_entry), time_name_compar);
/*
* 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.
*/
MirroredFlatFile_Open(
&mirroredOpen,
"global",
"pg_auth_time_constraint",
O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY,
S_IRUSR | S_IWUSR,
/* suppressError */ false,
/* atomic operation */ true,
/*isMirrorRecovery */ false);
for (curr_constraint = 0; curr_constraint < total_constraints; curr_constraint++)
{
authtime_entry *atime = &authtime_info[curr_constraint];
char startday[2], endday[2];
char *starttime, *endtime;
sputs_quote(&buffer, atime->rolname);
appendStringInfoChar(&buffer, ' ');
snprintf(&startday[0], 2, "%d", atime->startday);
sputs_quote(&buffer, startday);
appendStringInfoChar(&buffer, ' ');
starttime = DatumGetCString(DirectFunctionCall1(time_out,
TimeADTGetDatum(atime->starttime)));
sputs_quote(&buffer, starttime);
appendStringInfoChar(&buffer, ' ');
snprintf(&endday[0], 2, "%d", atime->endday);
sputs_quote(&buffer, endday);
appendStringInfoChar(&buffer, ' ');
endtime = DatumGetCString(DirectFunctionCall1(time_out,
TimeADTGetDatum(atime->endtime)));
sputs_quote(&buffer, endtime);
appendStringInfoChar(&buffer, '\n');
}
MirroredFlatFile_Append(&mirroredOpen, buffer.data, buffer.len,
/* suppressError */ false);
MirroredFlatFile_Flush(&mirroredOpen, /* suppressError */ false);
MirroredFlatFile_Close(&mirroredOpen);
}
/*
* write_filesystem_file: update the flat filesystem file
*/
static void
write_filesystem_file(Relation rfsys)
{
StringInfoData buffer;
HeapScanDesc scan;
TupleDesc dsc;
HeapTuple tuple;
MirroredFlatFileOpen mirroredOpen;
initStringInfo(&buffer);
MirroredFlatFile_Open(
&mirroredOpen,
"global",
"pg_filesystem",
O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY,
S_IRUSR | S_IWUSR,
/* suppressError */ false,
/* atomic operation */ true,
/*isMirrorRecovery */ false);
/*
* Read pg_filesystem and write the file.
*/
dsc = RelationGetDescr(rfsys);
scan = heap_beginscan(rfsys, SnapshotNow, 0, NULL);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
Form_pg_filesystem fsysform = (Form_pg_filesystem) GETSTRUCT(tuple);
char *fsysname;
Datum funcDatum;
char *funcName;
Datum fsysLibFileDatum;
char *fsysLibFile;
bool isNull;
fsysname = NameStr(fsysform->fsysname);
/*
* Check for illegal characters in the fsysname name.
*/
if (!name_okay(fsysname))
{
ereport(LOG,
(errmsg("invalid filesystem name \"%s\"", fsysname)));
continue;
}
/*
* The file format is: "filesystem_name" "fsys_libfile" "func1_funcname" "func2_funcname" ...
*/
sputs_quote(&buffer, fsysname);
/* fsys_libfile */
fsysLibFileDatum = heap_getattr(tuple, Anum_pg_filesystem_fsyslibfile, dsc, &isNull);
if(isNull)
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("filesystem \"%s\" has no libfile specified", fsysname)));
}
fsysLibFile = TextDatumGetCString(fsysLibFileDatum);
appendStringInfoChar(&buffer, ' ');
sputs_quote(&buffer, fsysLibFile);
/* fsys functions */
for(int i = 0; i < FSYS_FUNC_TOTALNUM; i++)
{
funcDatum = heap_getattr(tuple, fsys_func_type_to_attnum(i), dsc, &isNull);
if(isNull)
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("filesystem \"%s\" has no %s function defined", fsysname,
fsys_func_type_to_name(i))));
}
funcName = NameStr(*(DatumGetName(funcDatum)));
appendStringInfoChar(&buffer, ' ');
sputs_quote(&buffer, funcName);
}
appendStringInfoChar(&buffer, '\n');
}
heap_endscan(scan);
MirroredFlatFile_Append(&mirroredOpen, buffer.data, buffer.len,
/* suppressError */ false);
MirroredFlatFile_Flush(&mirroredOpen, /* suppressError */ false);
MirroredFlatFile_Close(&mirroredOpen);
if (buffer.maxlen > 0)
pfree(buffer.data);
}
/*
* 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.
*
* We also cause relcache init files to be flushed, for largely the same
* reasons.
*
* 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,
rel_authtime;
/*
* We don't have any hope of running a real relcache, but we can use the
* same fake-relcache facility that WAL replay uses.
*/
XLogInitRelationCache();
/* 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;
/* No locking is needed because no one else is alive yet */
rel_db = XLogOpenRelation(rnode);
write_database_file(rel_db, true);
if (!database_only)
{
/* hard-wired path to pg_authid */
rnode.spcNode = GLOBALTABLESPACE_OID;
rnode.dbNode = 0;
rnode.relNode = AuthIdRelationId;
rel_authid = XLogOpenRelation(rnode);
/* hard-wired path to pg_auth_members */
rnode.spcNode = GLOBALTABLESPACE_OID;
rnode.dbNode = 0;
rnode.relNode = AuthMemRelationId;
rel_authmem = XLogOpenRelation(rnode);
write_auth_file(rel_authid, rel_authmem);
/* hard-wired path to pg_auth_time_constraint */
rnode.spcNode = GLOBALTABLESPACE_OID;
rnode.dbNode = 0;
rnode.relNode = AuthTimeConstraintRelationId;
rel_authtime = XLogOpenRelation(rnode);
write_auth_time_file(rel_authid, rel_authtime);
}
CurrentResourceOwner = NULL;
ResourceOwnerDelete(owner);
XLogCloseRelationCache();
}
/*
* 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;
Relation trel = NULL;
Relation fsysrel = NULL;
if (database_file_update_subid == InvalidSubTransactionId &&
auth_file_update_subid == InvalidSubTransactionId &&
auth_time_file_update_subid == InvalidSubTransactionId &&
filesystem_file_update_subid == InvalidSubTransactionId)
return; /* nothing to do */
if (!isCommit)
{
database_file_update_subid = InvalidSubTransactionId;
auth_file_update_subid = InvalidSubTransactionId;
auth_time_file_update_subid = InvalidSubTransactionId;
filesystem_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)
mrel = heap_open(AuthMemRelationId, AccessShareLock);
if (auth_time_file_update_subid != InvalidSubTransactionId)
trel = heap_open(AuthTimeConstraintRelationId, AccessShareLock);
if (auth_file_update_subid != InvalidSubTransactionId ||
auth_time_file_update_subid != InvalidSubTransactionId)
arel = heap_open(AuthIdRelationId, AccessShareLock);
if (filesystem_file_update_subid != InvalidSubTransactionId)
{
fsysrel = heap_open(FileSystemRelationId, 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);
if (auth_time_file_update_subid != InvalidSubTransactionId)
LockSharedObject(AuthTimeConstraintRelationId, InvalidOid, 0,
AccessExclusiveLock);
if (filesystem_file_update_subid != InvalidSubTransactionId)
{
LockSharedObject(FileSystemRelationId, InvalidOid, 0,
AccessExclusiveLock);
}
/* Okay to write the files */
if (database_file_update_subid != InvalidSubTransactionId)
{
database_file_update_subid = InvalidSubTransactionId;
write_database_file(drel, false);
heap_close(drel, NoLock);
}
if (auth_file_update_subid != InvalidSubTransactionId)
{
auth_file_update_subid = InvalidSubTransactionId;
write_auth_file(arel, mrel);
heap_close(mrel, NoLock);
}
if (auth_time_file_update_subid != InvalidSubTransactionId)
{
auth_time_file_update_subid = InvalidSubTransactionId;
write_auth_time_file(arel, trel);
heap_close(trel, NoLock);
}
if (filesystem_file_update_subid != InvalidSubTransactionId)
{
filesystem_file_update_subid = InvalidSubTransactionId;
write_filesystem_file(fsysrel);
heap_close(fsysrel, NoLock);
}
if (arel != NULL)
heap_close(arel, NoLock);
/*
* Signal the postmaster to reload its caches.
*/
SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);
}
/*
* 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 (auth_time_file_update_subid != InvalidSubTransactionId)
{
auth_time_file_update_subid = InvalidSubTransactionId;
info |= FF_BIT_AUTH_TIME;
}
if (filesystem_file_update_subid != InvalidSubTransactionId)
{
filesystem_file_update_subid = InvalidSubTransactionId;
info |= FF_BIT_FILESYSTEM;
}
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;
if (auth_file_update_subid == mySubid)
auth_time_file_update_subid = parentSubid;
if (filesystem_file_update_subid == mySubid)
filesystem_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;
if (auth_time_file_update_subid == mySubid)
auth_time_file_update_subid = InvalidSubTransactionId;
if (filesystem_file_update_subid == mySubid)
filesystem_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;
case AuthTimeConstraintRelationId:
auth_time_file_update_needed();
break;
case FileSystemRelationId:
filesystem_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();
if (info & FF_BIT_AUTH_TIME)
auth_time_file_update_needed();
if (info & FF_BIT_FILESYSTEM)
filesystem_file_update_needed();
}