📄 flatfiles.c
字号:
/*------------------------------------------------------------------------- * * 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-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.15.2.1 2005/11/22 18:23:24 momjian Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include <sys/stat.h>#include <unistd.h>#include "access/heapam.h"#include "access/twophase_rmgr.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/fd.h"#include "storage/pmsignal.h"#include "utils/acl.h"#include "utils/builtins.h"#include "utils/flatfiles.h"#include "utils/resowner.h"#include "utils/syscache.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) */voiddatabase_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) */voidauth_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 voidfputs_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 boolname_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. */static voidwrite_database_file(Relation drel){ char *filename, *tempname; int bufsize; FILE *fp; mode_t oumask; HeapScanDesc scan; HeapTuple tuple; NameData oldest_datname; TransactionId oldest_datfrozenxid = InvalidTransactionId; /* * 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, datvacuumxid; datname = NameStr(dbform->datname); datoid = HeapTupleGetOid(tuple); dattablespace = dbform->dattablespace; datfrozenxid = dbform->datfrozenxid; datvacuumxid = dbform->datvacuumxid; /* * Identify the oldest datfrozenxid, ignoring databases that are not * connectable (we assume they are safely frozen). This must match * the logic in vac_truncate_clog() in vacuum.c. */ if (dbform->datallowconn && 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 vacuumxid * * 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 %u\n", datoid, dattablespace, datfrozenxid, datvacuumxid); } 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))); /* * 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; 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 intoid_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 intname_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 intmem_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 voidwrite_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. 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].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, 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 { /*
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -