1 /*-------------------------------------------------------------------------
4 * Routines for maintaining "flat file" images of the shared catalogs.
6 * We use flat files so that the postmaster and not-yet-fully-started
7 * backends can look at the contents of pg_database, pg_shadow, and pg_group
8 * for authentication purposes. This module is responsible for keeping the
9 * flat-file images as nearly in sync with database reality as possible.
11 * The tricky part of the write_xxx_file() routines in this module is that
12 * they need to be able to operate in the context of the database startup
13 * process (which calls BuildFlatFiles()) as well as a normal backend.
14 * This means for example that we can't assume a fully functional relcache
15 * and we can't use syscaches at all. The major restriction imposed by
16 * all that is that there's no way to read an out-of-line-toasted datum,
17 * because the tuptoaster.c code is not prepared to cope with such an
18 * environment. Fortunately we can design the shared catalogs in such
19 * a way that this is OK.
22 * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
23 * Portions Copyright (c) 1994, Regents of the University of California
25 * $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.2 2005/02/20 04:45:59 tgl Exp $
27 *-------------------------------------------------------------------------
34 #include "access/heapam.h"
35 #include "catalog/catname.h"
36 #include "catalog/pg_database.h"
37 #include "catalog/pg_group.h"
38 #include "catalog/pg_namespace.h"
39 #include "catalog/pg_shadow.h"
40 #include "catalog/pg_tablespace.h"
41 #include "commands/trigger.h"
42 #include "miscadmin.h"
43 #include "storage/fd.h"
44 #include "storage/pmsignal.h"
45 #include "utils/acl.h"
46 #include "utils/builtins.h"
47 #include "utils/flatfiles.h"
48 #include "utils/resowner.h"
49 #include "utils/syscache.h"
52 #define DATABASE_FLAT_FILE "pg_database"
53 #define GROUP_FLAT_FILE "pg_group"
54 #define USER_FLAT_FILE "pg_pwd"
58 * The need-to-update-files flags are SubTransactionIds that show
59 * what level of the subtransaction tree requested the update. To register
60 * an update, the subtransaction saves its own SubTransactionId in the flag,
61 * unless the value was already set to a valid SubTransactionId (which implies
62 * that it or a parent level has already requested the same). If it aborts
63 * and the value is its SubTransactionId, it resets the flag to
64 * InvalidSubTransactionId. If it commits, it changes the value to its
65 * parent's SubTransactionId. This way the value is propagated up to the
66 * top-level transaction, which will update the files if a valid
67 * SubTransactionId is seen at top-level commit.
69 static SubTransactionId database_file_update_subid = InvalidSubTransactionId;
70 static SubTransactionId group_file_update_subid = InvalidSubTransactionId;
71 static SubTransactionId user_file_update_subid = InvalidSubTransactionId;
75 * Mark flat database file as needing an update (because pg_database changed)
78 database_file_update_needed(void)
80 if (database_file_update_subid == InvalidSubTransactionId)
81 database_file_update_subid = GetCurrentSubTransactionId();
85 * Mark flat group file as needing an update (because pg_group changed)
88 group_file_update_needed(void)
90 if (group_file_update_subid == InvalidSubTransactionId)
91 group_file_update_subid = GetCurrentSubTransactionId();
95 * Mark flat user file as needing an update (because pg_shadow changed)
98 user_file_update_needed(void)
100 if (user_file_update_subid == InvalidSubTransactionId)
101 user_file_update_subid = GetCurrentSubTransactionId();
106 * database_getflatfilename --- get full pathname of database file
108 * Note that result string is palloc'd, and should be freed by the caller.
111 database_getflatfilename(void)
116 bufsize = strlen(DataDir) + strlen("/global/") +
117 strlen(DATABASE_FLAT_FILE) + 1;
118 pfnam = (char *) palloc(bufsize);
119 snprintf(pfnam, bufsize, "%s/global/%s", DataDir, DATABASE_FLAT_FILE);
125 * group_getflatfilename --- get full pathname of group file
127 * Note that result string is palloc'd, and should be freed by the caller.
130 group_getflatfilename(void)
135 bufsize = strlen(DataDir) + strlen("/global/") +
136 strlen(GROUP_FLAT_FILE) + 1;
137 pfnam = (char *) palloc(bufsize);
138 snprintf(pfnam, bufsize, "%s/global/%s", DataDir, GROUP_FLAT_FILE);
144 * Get full pathname of password file.
146 * Note that result string is palloc'd, and should be freed by the caller.
149 user_getflatfilename(void)
154 bufsize = strlen(DataDir) + strlen("/global/") +
155 strlen(USER_FLAT_FILE) + 1;
156 pfnam = (char *) palloc(bufsize);
157 snprintf(pfnam, bufsize, "%s/global/%s", DataDir, USER_FLAT_FILE);
166 * Outputs string in quotes, with double-quotes duplicated.
167 * We could use quote_ident(), but that expects a TEXT argument.
170 fputs_quote(const char *str, FILE *fp)
186 * We must disallow newlines in user and group names because
187 * hba.c's parser won't handle fields split across lines, even if quoted.
190 name_okay(const char *str)
194 i = strcspn(str, "\r\n");
195 return (str[i] == '\0');
200 * write_database_file: update the flat database file
202 * A side effect is to determine the oldest database's datfrozenxid
203 * so we can set or update the XID wrap limit.
206 write_database_file(Relation drel)
215 NameData oldest_datname;
216 TransactionId oldest_datfrozenxid = InvalidTransactionId;
219 * Create a temporary filename to be renamed later. This prevents the
220 * backend from clobbering the flat file while the postmaster
221 * might be reading from it.
223 filename = database_getflatfilename();
224 bufsize = strlen(filename) + 12;
225 tempname = (char *) palloc(bufsize);
226 snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
228 oumask = umask((mode_t) 077);
229 fp = AllocateFile(tempname, "w");
233 (errcode_for_file_access(),
234 errmsg("could not write to temporary file \"%s\": %m",
238 * Read pg_database and write the file. Note we use SnapshotSelf to
239 * ensure we see all effects of current transaction. (Perhaps could
240 * do a CommandCounterIncrement beforehand, instead?)
242 scan = heap_beginscan(drel, SnapshotSelf, 0, NULL);
243 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
245 Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
248 TransactionId datfrozenxid;
250 datname = NameStr(dbform->datname);
251 datoid = HeapTupleGetOid(tuple);
252 datfrozenxid = dbform->datfrozenxid;
255 * Identify the oldest datfrozenxid, ignoring databases that are not
256 * connectable (we assume they are safely frozen). This must match
257 * the logic in vac_truncate_clog() in vacuum.c.
259 if (dbform->datallowconn &&
260 TransactionIdIsNormal(datfrozenxid))
262 if (oldest_datfrozenxid == InvalidTransactionId ||
263 TransactionIdPrecedes(datfrozenxid, oldest_datfrozenxid))
265 oldest_datfrozenxid = datfrozenxid;
266 namestrcpy(&oldest_datname, datname);
271 * Check for illegal characters in the database name.
273 if (!name_okay(datname))
276 (errmsg("invalid database name \"%s\"", datname)));
281 * The file format is: "dbname" oid frozenxid
283 * The xid is not needed for backend startup, but may be of use
284 * for forensic purposes.
286 fputs_quote(datname, fp);
287 fprintf(fp, " %u %u\n", datoid, datfrozenxid);
293 (errcode_for_file_access(),
294 errmsg("could not write to temporary file \"%s\": %m",
298 * Rename the temp file to its final name, deleting the old flat file.
299 * We expect that rename(2) is an atomic action.
301 if (rename(tempname, filename))
303 (errcode_for_file_access(),
304 errmsg("could not rename file \"%s\" to \"%s\": %m",
305 tempname, filename)));
311 * Set the transaction ID wrap limit using the oldest datfrozenxid
313 if (oldest_datfrozenxid != InvalidTransactionId)
314 SetTransactionIdLimit(oldest_datfrozenxid, &oldest_datname);
319 * write_group_file: update the flat group file
322 write_group_file(Relation grel)
333 * Create a temporary filename to be renamed later. This prevents the
334 * backend from clobbering the flat file while the postmaster
335 * might be reading from it.
337 filename = group_getflatfilename();
338 bufsize = strlen(filename) + 12;
339 tempname = (char *) palloc(bufsize);
340 snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
342 oumask = umask((mode_t) 077);
343 fp = AllocateFile(tempname, "w");
347 (errcode_for_file_access(),
348 errmsg("could not write to temporary file \"%s\": %m",
352 * Read pg_group and write the file. Note we use SnapshotSelf to
353 * ensure we see all effects of current transaction. (Perhaps could
354 * do a CommandCounterIncrement beforehand, instead?)
356 scan = heap_beginscan(grel, SnapshotSelf, 0, NULL);
357 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
359 Form_pg_group grpform = (Form_pg_group) GETSTRUCT(tuple);
360 HeapTupleHeader tup = tuple->t_data;
361 char *tp; /* ptr to tuple data */
362 long off; /* offset in tuple data */
363 bits8 *bp = tup->t_bits; /* ptr to null bitmask in tuple */
371 groname = NameStr(grpform->groname);
374 * Check for illegal characters in the group name.
376 if (!name_okay(groname))
379 (errmsg("invalid group name \"%s\"", groname)));
384 * We can't use heap_getattr() here because during startup we will
385 * not have any tupdesc for pg_group. Fortunately it's not too
386 * hard to work around this. grolist is the first possibly-null
387 * field so we can compute its offset directly.
389 tp = (char *) tup + tup->t_hoff;
390 off = offsetof(FormData_pg_group, grolist);
392 if (HeapTupleHasNulls(tuple) &&
393 att_isnull(Anum_pg_group_grolist - 1, bp))
395 /* grolist is null, so we can ignore this group */
399 /* assume grolist is pass-by-ref */
400 datum = PointerGetDatum(tp + off);
403 * We can't currently support out-of-line toasted group lists in
404 * startup mode (the tuptoaster won't work). This sucks, but it
405 * should be something of a corner case. Live with it until we
406 * can redesign pg_group.
408 * Detect startup mode by noting whether we got a tupdesc.
410 if (VARATT_IS_EXTERNAL(DatumGetPointer(datum)) &&
411 RelationGetDescr(grel) == NULL)
414 /* be sure the IdList is not toasted */
415 grolist_p = DatumGetIdListP(datum);
418 * The file format is: "groupname" usesysid1 usesysid2 ...
420 * We ignore groups that have no members.
422 aidp = IDLIST_DAT(grolist_p);
423 num = IDLIST_NUM(grolist_p);
426 fputs_quote(groname, fp);
427 fprintf(fp, "\t%u", aidp[0]);
428 for (i = 1; i < num; ++i)
429 fprintf(fp, " %u", aidp[i]);
433 /* if IdList was toasted, free detoasted copy */
434 if ((Pointer) grolist_p != DatumGetPointer(datum))
441 (errcode_for_file_access(),
442 errmsg("could not write to temporary file \"%s\": %m",
446 * Rename the temp file to its final name, deleting the old flat file.
447 * We expect that rename(2) is an atomic action.
449 if (rename(tempname, filename))
451 (errcode_for_file_access(),
452 errmsg("could not rename file \"%s\" to \"%s\": %m",
453 tempname, filename)));
461 * write_user_file: update the flat password file
464 write_user_file(Relation urel)
475 * Create a temporary filename to be renamed later. This prevents the
476 * backend from clobbering the flat file while the postmaster might
477 * be reading from it.
479 filename = user_getflatfilename();
480 bufsize = strlen(filename) + 12;
481 tempname = (char *) palloc(bufsize);
482 snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
484 oumask = umask((mode_t) 077);
485 fp = AllocateFile(tempname, "w");
489 (errcode_for_file_access(),
490 errmsg("could not write to temporary file \"%s\": %m",
494 * Read pg_shadow and write the file. Note we use SnapshotSelf to
495 * ensure we see all effects of current transaction. (Perhaps could
496 * do a CommandCounterIncrement beforehand, instead?)
498 scan = heap_beginscan(urel, SnapshotSelf, 0, NULL);
499 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
501 Form_pg_shadow pwform = (Form_pg_shadow) GETSTRUCT(tuple);
502 HeapTupleHeader tup = tuple->t_data;
503 char *tp; /* ptr to tuple data */
504 long off; /* offset in tuple data */
505 bits8 *bp = tup->t_bits; /* ptr to null bitmask in tuple */
512 usename = NameStr(pwform->usename);
513 usesysid = pwform->usesysid;
516 * We can't use heap_getattr() here because during startup we will
517 * not have any tupdesc for pg_shadow. Fortunately it's not too
518 * hard to work around this. passwd is the first possibly-null
519 * field so we can compute its offset directly.
521 tp = (char *) tup + tup->t_hoff;
522 off = offsetof(FormData_pg_shadow, passwd);
524 if (HeapTupleHasNulls(tuple) &&
525 att_isnull(Anum_pg_shadow_passwd - 1, bp))
527 /* passwd is null, emit as an empty string */
528 passwd = pstrdup("");
532 /* assume passwd is pass-by-ref */
533 datum = PointerGetDatum(tp + off);
536 * The password probably shouldn't ever be out-of-line toasted;
537 * if it is, ignore it, since we can't handle that in startup mode.
539 if (VARATT_IS_EXTERNAL(DatumGetPointer(datum)))
540 passwd = pstrdup("");
542 passwd = DatumGetCString(DirectFunctionCall1(textout, datum));
544 /* assume passwd has attlen -1 */
545 off = att_addlength(off, -1, tp + off);
548 if (HeapTupleHasNulls(tuple) &&
549 att_isnull(Anum_pg_shadow_valuntil - 1, bp))
551 /* valuntil is null, emit as an empty string */
552 valuntil = pstrdup("");
556 /* assume valuntil has attalign 'i' */
557 off = att_align(off, 'i');
558 /* assume valuntil is pass-by-value, integer size */
559 datum = Int32GetDatum(*((int32 *) (tp + off)));
560 valuntil = DatumGetCString(DirectFunctionCall1(abstimeout, datum));
564 * Check for illegal characters in the user name and password.
566 if (!name_okay(usename))
569 (errmsg("invalid user name \"%s\"", usename)));
572 if (!name_okay(passwd))
575 (errmsg("invalid user password \"%s\"", passwd)));
580 * The file format is: "usename" usesysid "passwd" "valuntil"
582 fputs_quote(usename, fp);
583 fprintf(fp, " %u ", usesysid);
584 fputs_quote(passwd, fp);
586 fputs_quote(valuntil, fp);
596 (errcode_for_file_access(),
597 errmsg("could not write to temporary file \"%s\": %m",
601 * Rename the temp file to its final name, deleting the old flat file.
602 * We expect that rename(2) is an atomic action.
604 if (rename(tempname, filename))
606 (errcode_for_file_access(),
607 errmsg("could not rename file \"%s\" to \"%s\": %m",
608 tempname, filename)));
616 * This routine is called once during database startup, after completing
617 * WAL replay if needed. Its purpose is to sync the flat files with the
618 * current state of the database tables. This is particularly important
619 * during PITR operation, since the flat files will come from the
620 * base backup which may be far out of sync with the current state.
622 * In theory we could skip rebuilding the flat files if no WAL replay
623 * occurred, but it seems safest to just do it always. We have to
624 * scan pg_database to compute the XID wrap limit anyway.
626 * In a standalone backend we pass database_only = true to skip processing
627 * the user and group files. We won't need them, and building them could
628 * fail if there's something corrupt in those catalogs.
631 BuildFlatFiles(bool database_only)
638 * We don't have any hope of running a real relcache, but we can use
639 * the same fake-relcache facility that WAL replay uses.
641 XLogInitRelationCache();
643 /* Need a resowner to keep the heapam and buffer code happy */
644 owner = ResourceOwnerCreate(NULL, "BuildFlatFiles");
645 CurrentResourceOwner = owner;
647 /* hard-wired path to pg_database */
648 rnode.spcNode = GLOBALTABLESPACE_OID;
650 rnode.relNode = RelOid_pg_database;
652 /* No locking is needed because no one else is alive yet */
653 rel = XLogOpenRelation(true, 0, rnode);
654 write_database_file(rel);
658 /* hard-wired path to pg_group */
659 rnode.spcNode = GLOBALTABLESPACE_OID;
661 rnode.relNode = RelOid_pg_group;
663 rel = XLogOpenRelation(true, 0, rnode);
664 write_group_file(rel);
666 /* hard-wired path to pg_shadow */
667 rnode.spcNode = GLOBALTABLESPACE_OID;
669 rnode.relNode = RelOid_pg_shadow;
671 rel = XLogOpenRelation(true, 0, rnode);
672 write_user_file(rel);
675 CurrentResourceOwner = NULL;
676 ResourceOwnerDelete(owner);
678 XLogCloseRelationCache();
683 * This routine is called during transaction commit or abort.
685 * On commit, if we've written any of the critical database tables during
686 * the current transaction, update the flat files and signal the postmaster.
688 * On abort, just reset the static flags so we don't try to do it on the
689 * next successful commit.
691 * NB: this should be the last step before actual transaction commit.
692 * If any error aborts the transaction after we run this code, the postmaster
693 * will still have received and cached the changed data; so minimize the
694 * window for such problems.
697 AtEOXact_UpdateFlatFiles(bool isCommit)
699 Relation drel = NULL;
700 Relation grel = NULL;
701 Relation urel = NULL;
703 if (database_file_update_subid == InvalidSubTransactionId &&
704 group_file_update_subid == InvalidSubTransactionId &&
705 user_file_update_subid == InvalidSubTransactionId)
706 return; /* nothing to do */
710 database_file_update_subid = InvalidSubTransactionId;
711 group_file_update_subid = InvalidSubTransactionId;
712 user_file_update_subid = InvalidSubTransactionId;
717 * We use ExclusiveLock to ensure that only one backend writes the
718 * flat file(s) at a time. That's sufficient because it's okay to
719 * allow plain reads of the tables in parallel. There is some chance
720 * of a deadlock here (if we were triggered by a user update of one
721 * of the tables, which likely won't have gotten a strong enough lock),
722 * so get the locks we need before writing anything.
724 if (database_file_update_subid != InvalidSubTransactionId)
725 drel = heap_openr(DatabaseRelationName, ExclusiveLock);
726 if (group_file_update_subid != InvalidSubTransactionId)
727 grel = heap_openr(GroupRelationName, ExclusiveLock);
728 if (user_file_update_subid != InvalidSubTransactionId)
729 urel = heap_openr(ShadowRelationName, ExclusiveLock);
731 /* Okay to write the files */
732 if (database_file_update_subid != InvalidSubTransactionId)
734 database_file_update_subid = InvalidSubTransactionId;
735 write_database_file(drel);
736 heap_close(drel, NoLock);
739 if (group_file_update_subid != InvalidSubTransactionId)
741 group_file_update_subid = InvalidSubTransactionId;
742 write_group_file(grel);
743 heap_close(grel, NoLock);
746 if (user_file_update_subid != InvalidSubTransactionId)
748 user_file_update_subid = InvalidSubTransactionId;
749 write_user_file(urel);
750 heap_close(urel, NoLock);
754 * Signal the postmaster to reload its caches.
756 SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);
760 * AtEOSubXact_UpdateFlatFiles
762 * Called at subtransaction end, this routine resets or updates the
763 * need-to-update-files flags.
766 AtEOSubXact_UpdateFlatFiles(bool isCommit,
767 SubTransactionId mySubid,
768 SubTransactionId parentSubid)
772 if (database_file_update_subid == mySubid)
773 database_file_update_subid = parentSubid;
775 if (group_file_update_subid == mySubid)
776 group_file_update_subid = parentSubid;
778 if (user_file_update_subid == mySubid)
779 user_file_update_subid = parentSubid;
783 if (database_file_update_subid == mySubid)
784 database_file_update_subid = InvalidSubTransactionId;
786 if (group_file_update_subid == mySubid)
787 group_file_update_subid = InvalidSubTransactionId;
789 if (user_file_update_subid == mySubid)
790 user_file_update_subid = InvalidSubTransactionId;
796 * This trigger is fired whenever someone modifies pg_database, pg_shadow
797 * or pg_group via general-purpose INSERT/UPDATE/DELETE commands.
799 * It is sufficient for this to be a STATEMENT trigger since we don't
800 * care which individual rows changed. It doesn't much matter whether
801 * it's a BEFORE or AFTER trigger.
804 flatfile_update_trigger(PG_FUNCTION_ARGS)
806 TriggerData *trigdata = (TriggerData *) fcinfo->context;
808 if (!CALLED_AS_TRIGGER(fcinfo))
810 "flatfile_update_trigger was not called by trigger manager");
812 if (RelationGetNamespace(trigdata->tg_relation) != PG_CATALOG_NAMESPACE)
813 elog(ERROR, "flatfile_update_trigger was called for wrong table");
815 switch (RelationGetRelid(trigdata->tg_relation))
817 case RelOid_pg_database:
818 database_file_update_needed();
820 case RelOid_pg_group:
821 group_file_update_needed();
823 case RelOid_pg_shadow:
824 user_file_update_needed();
827 elog(ERROR, "flatfile_update_trigger was called for wrong table");
831 return PointerGetDatum(NULL);
836 * Old version of trigger --- remove after we can force an initdb
838 extern Datum update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS);
841 update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS)
843 return flatfile_update_trigger(fcinfo);