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_authid, and
8 * pg_auth_members for authentication purposes. This module is
9 * responsible for keeping the flat-file images as nearly in sync with
10 * database reality as possible.
12 * The tricky part of the write_xxx_file() routines in this module is that
13 * they need to be able to operate in the context of the database startup
14 * process (which calls BuildFlatFiles()) as well as a normal backend.
15 * This means for example that we can't assume a fully functional relcache
16 * and we can't use syscaches at all. The major restriction imposed by
17 * all that is that there's no way to read an out-of-line-toasted datum,
18 * because the tuptoaster.c code is not prepared to cope with such an
19 * environment. Fortunately we can design the shared catalogs in such
20 * a way that this is OK.
23 * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
24 * Portions Copyright (c) 1994, Regents of the University of California
26 * $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.19 2006/07/10 16:20:51 alvherre Exp $
28 *-------------------------------------------------------------------------
35 #include "access/heapam.h"
36 #include "access/twophase_rmgr.h"
37 #include "catalog/pg_auth_members.h"
38 #include "catalog/pg_authid.h"
39 #include "catalog/pg_database.h"
40 #include "catalog/pg_namespace.h"
41 #include "catalog/pg_tablespace.h"
42 #include "commands/trigger.h"
43 #include "miscadmin.h"
44 #include "storage/fd.h"
45 #include "storage/pmsignal.h"
46 #include "utils/acl.h"
47 #include "utils/builtins.h"
48 #include "utils/flatfiles.h"
49 #include "utils/resowner.h"
50 #include "utils/syscache.h"
53 /* Actual names of the flat files (within $PGDATA) */
54 #define DATABASE_FLAT_FILE "global/pg_database"
55 #define AUTH_FLAT_FILE "global/pg_auth"
57 /* Info bits in a flatfiles 2PC record */
58 #define FF_BIT_DATABASE 1
63 * The need-to-update-files flags are SubTransactionIds that show
64 * what level of the subtransaction tree requested the update. To register
65 * an update, the subtransaction saves its own SubTransactionId in the flag,
66 * unless the value was already set to a valid SubTransactionId (which implies
67 * that it or a parent level has already requested the same). If it aborts
68 * and the value is its SubTransactionId, it resets the flag to
69 * InvalidSubTransactionId. If it commits, it changes the value to its
70 * parent's SubTransactionId. This way the value is propagated up to the
71 * top-level transaction, which will update the files if a valid
72 * SubTransactionId is seen at top-level commit.
74 static SubTransactionId database_file_update_subid = InvalidSubTransactionId;
75 static SubTransactionId auth_file_update_subid = InvalidSubTransactionId;
79 * Mark flat database file as needing an update (because pg_database changed)
82 database_file_update_needed(void)
84 if (database_file_update_subid == InvalidSubTransactionId)
85 database_file_update_subid = GetCurrentSubTransactionId();
89 * Mark flat auth file as needing an update (because pg_authid or
90 * pg_auth_members changed)
93 auth_file_update_needed(void)
95 if (auth_file_update_subid == InvalidSubTransactionId)
96 auth_file_update_subid = GetCurrentSubTransactionId();
101 * database_getflatfilename --- get pathname of database file
103 * Note that result string is palloc'd, and should be freed by the caller.
104 * (This convention is not really needed anymore, since the relative path
108 database_getflatfilename(void)
110 return pstrdup(DATABASE_FLAT_FILE);
114 * auth_getflatfilename --- get pathname of auth file
116 * Note that result string is palloc'd, and should be freed by the caller.
117 * (This convention is not really needed anymore, since the relative path
121 auth_getflatfilename(void)
123 return pstrdup(AUTH_FLAT_FILE);
130 * Outputs string in quotes, with double-quotes duplicated.
131 * We could use quote_ident(), but that expects a TEXT argument.
134 fputs_quote(const char *str, FILE *fp)
150 * We must disallow newlines in role names because
151 * hba.c's parser won't handle fields split across lines, even if quoted.
154 name_okay(const char *str)
158 i = strcspn(str, "\r\n");
159 return (str[i] == '\0');
164 * write_database_file: update the flat database file
166 * A side effect is to determine the oldest database's datminxid
167 * so we can set or update the XID wrap limit.
170 write_database_file(Relation drel)
179 NameData oldest_datname;
180 TransactionId oldest_datminxid = InvalidTransactionId;
183 * Create a temporary filename to be renamed later. This prevents the
184 * backend from clobbering the flat file while the postmaster might be
187 filename = database_getflatfilename();
188 bufsize = strlen(filename) + 12;
189 tempname = (char *) palloc(bufsize);
190 snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
192 oumask = umask((mode_t) 077);
193 fp = AllocateFile(tempname, "w");
197 (errcode_for_file_access(),
198 errmsg("could not write to temporary file \"%s\": %m",
202 * Read pg_database and write the file.
204 scan = heap_beginscan(drel, SnapshotNow, 0, NULL);
205 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
207 Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
211 TransactionId datminxid,
214 datname = NameStr(dbform->datname);
215 datoid = HeapTupleGetOid(tuple);
216 dattablespace = dbform->dattablespace;
217 datminxid = dbform->datminxid;
218 datvacuumxid = dbform->datvacuumxid;
221 * Identify the oldest datminxid, ignoring databases that are not
222 * connectable (we assume they are safely frozen). This must match
223 * the logic in vac_truncate_clog() in vacuum.c.
225 if (dbform->datallowconn &&
226 TransactionIdIsNormal(datminxid))
228 if (oldest_datminxid == InvalidTransactionId ||
229 TransactionIdPrecedes(datminxid, oldest_datminxid))
231 oldest_datminxid = datminxid;
232 namestrcpy(&oldest_datname, datname);
237 * Check for illegal characters in the database name.
239 if (!name_okay(datname))
242 (errmsg("invalid database name \"%s\"", datname)));
247 * The file format is: "dbname" oid tablespace minxid vacuumxid
249 * The xids are not needed for backend startup, but are of use to
250 * autovacuum, and might also be helpful for forensic purposes.
252 fputs_quote(datname, fp);
253 fprintf(fp, " %u %u %u %u\n",
254 datoid, dattablespace, datminxid, datvacuumxid);
260 (errcode_for_file_access(),
261 errmsg("could not write to temporary file \"%s\": %m",
265 * Rename the temp file to its final name, deleting the old flat file. We
266 * expect that rename(2) is an atomic action.
268 if (rename(tempname, filename))
270 (errcode_for_file_access(),
271 errmsg("could not rename file \"%s\" to \"%s\": %m",
272 tempname, filename)));
275 * Set the transaction ID wrap limit using the oldest datminxid
277 if (oldest_datminxid != InvalidTransactionId)
278 SetTransactionIdLimit(oldest_datminxid, &oldest_datname);
283 * Support for write_auth_file
285 * The format for the flat auth file is
286 * "rolename" "password" "validuntil" "memberof" "memberof" ...
287 * Only roles that are marked rolcanlogin are entered into the auth file.
288 * Each role's line lists all the roles (groups) of which it is directly
289 * or indirectly a member, except for itself.
291 * The postmaster expects the file to be sorted by rolename. There is not
292 * any special ordering of the membership lists.
294 * To construct this information, we scan pg_authid and pg_auth_members,
295 * and build data structures in-memory before writing the file.
315 /* qsort comparator for sorting auth_entry array by roleid */
317 oid_compar(const void *a, const void *b)
319 const auth_entry *a_auth = (const auth_entry *) a;
320 const auth_entry *b_auth = (const auth_entry *) b;
322 if (a_auth->roleid < b_auth->roleid)
324 if (a_auth->roleid > b_auth->roleid)
329 /* qsort comparator for sorting auth_entry array by rolname */
331 name_compar(const void *a, const void *b)
333 const auth_entry *a_auth = (const auth_entry *) a;
334 const auth_entry *b_auth = (const auth_entry *) b;
336 return strcmp(a_auth->rolname, b_auth->rolname);
339 /* qsort comparator for sorting authmem_entry array by memberid */
341 mem_compar(const void *a, const void *b)
343 const authmem_entry *a_auth = (const authmem_entry *) a;
344 const authmem_entry *b_auth = (const authmem_entry *) b;
346 if (a_auth->memberid < b_auth->memberid)
348 if (a_auth->memberid > b_auth->memberid)
355 * write_auth_file: update the flat auth file
358 write_auth_file(Relation rel_authid, Relation rel_authmem)
363 BlockNumber totalblocks;
373 auth_entry *auth_info;
374 authmem_entry *authmem_info;
377 * Create a temporary filename to be renamed later. This prevents the
378 * backend from clobbering the flat file while the postmaster might be
381 filename = auth_getflatfilename();
382 bufsize = strlen(filename) + 12;
383 tempname = (char *) palloc(bufsize);
384 snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
386 oumask = umask((mode_t) 077);
387 fp = AllocateFile(tempname, "w");
391 (errcode_for_file_access(),
392 errmsg("could not write to temporary file \"%s\": %m",
396 * Read pg_authid and fill temporary data structures. Note we must read
397 * all roles, even those without rolcanlogin.
399 totalblocks = RelationGetNumberOfBlocks(rel_authid);
400 totalblocks = totalblocks ? totalblocks : 1;
401 est_rows = totalblocks * (BLCKSZ / (sizeof(HeapTupleHeaderData) + sizeof(FormData_pg_authid)));
402 auth_info = (auth_entry *) palloc(est_rows * sizeof(auth_entry));
404 scan = heap_beginscan(rel_authid, SnapshotNow, 0, NULL);
405 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
407 Form_pg_authid aform = (Form_pg_authid) GETSTRUCT(tuple);
408 HeapTupleHeader tup = tuple->t_data;
409 char *tp; /* ptr to tuple data */
410 long off; /* offset in tuple data */
411 bits8 *bp = tup->t_bits; /* ptr to null bitmask in tuple */
414 if (curr_role >= est_rows)
417 auth_info = (auth_entry *)
418 repalloc(auth_info, est_rows * sizeof(auth_entry));
421 auth_info[curr_role].roleid = HeapTupleGetOid(tuple);
422 auth_info[curr_role].rolcanlogin = aform->rolcanlogin;
423 auth_info[curr_role].rolname = pstrdup(NameStr(aform->rolname));
424 auth_info[curr_role].member_of = NIL;
427 * We can't use heap_getattr() here because during startup we will not
428 * have any tupdesc for pg_authid. Fortunately it's not too hard to
429 * work around this. rolpassword is the first possibly-null field so
430 * we can compute its offset directly.
432 tp = (char *) tup + tup->t_hoff;
433 off = offsetof(FormData_pg_authid, rolpassword);
435 if (HeapTupleHasNulls(tuple) &&
436 att_isnull(Anum_pg_authid_rolpassword - 1, bp))
438 /* passwd is null, emit as an empty string */
439 auth_info[curr_role].rolpassword = pstrdup("");
443 /* assume passwd is pass-by-ref */
444 datum = PointerGetDatum(tp + off);
447 * The password probably shouldn't ever be out-of-line toasted; if
448 * it is, ignore it, since we can't handle that in startup mode.
450 if (VARATT_IS_EXTERNAL(DatumGetPointer(datum)))
451 auth_info[curr_role].rolpassword = pstrdup("");
453 auth_info[curr_role].rolpassword = DatumGetCString(DirectFunctionCall1(textout, datum));
455 /* assume passwd has attlen -1 */
456 off = att_addlength(off, -1, tp + off);
459 if (HeapTupleHasNulls(tuple) &&
460 att_isnull(Anum_pg_authid_rolvaliduntil - 1, bp))
462 /* rolvaliduntil is null, emit as an empty string */
463 auth_info[curr_role].rolvaliduntil = pstrdup("");
468 * rolvaliduntil is timestamptz, which we assume is double
469 * alignment and pass-by-reference.
471 off = att_align(off, 'd');
472 datum = PointerGetDatum(tp + off);
473 auth_info[curr_role].rolvaliduntil = DatumGetCString(DirectFunctionCall1(timestamptz_out, datum));
477 * Check for illegal characters in the user name and password.
479 if (!name_okay(auth_info[curr_role].rolname))
482 (errmsg("invalid role name \"%s\"",
483 auth_info[curr_role].rolname)));
486 if (!name_okay(auth_info[curr_role].rolpassword))
489 (errmsg("invalid role password \"%s\"",
490 auth_info[curr_role].rolpassword)));
500 * Read pg_auth_members into temporary data structure, too
502 totalblocks = RelationGetNumberOfBlocks(rel_authmem);
503 totalblocks = totalblocks ? totalblocks : 1;
504 est_rows = totalblocks * (BLCKSZ / (sizeof(HeapTupleHeaderData) + sizeof(FormData_pg_auth_members)));
505 authmem_info = (authmem_entry *) palloc(est_rows * sizeof(authmem_entry));
507 scan = heap_beginscan(rel_authmem, SnapshotNow, 0, NULL);
508 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
510 Form_pg_auth_members memform = (Form_pg_auth_members) GETSTRUCT(tuple);
512 if (curr_mem >= est_rows)
515 authmem_info = (authmem_entry *)
516 repalloc(authmem_info, est_rows * sizeof(authmem_entry));
519 authmem_info[curr_mem].roleid = memform->roleid;
520 authmem_info[curr_mem].memberid = memform->member;
527 * Search for memberships. We can skip all this if pg_auth_members is
533 * Sort auth_info by roleid and authmem_info by memberid.
535 qsort(auth_info, total_roles, sizeof(auth_entry), oid_compar);
536 qsort(authmem_info, total_mem, sizeof(authmem_entry), mem_compar);
539 * For each role, find what it belongs to.
541 for (curr_role = 0; curr_role < total_roles; curr_role++)
544 List *roles_names_list = NIL;
547 /* We can skip this for non-login roles */
548 if (!auth_info[curr_role].rolcanlogin)
552 * This search algorithm is the same as in is_member_of_role; we
553 * are just working with a different input data structure.
555 roles_list = list_make1_oid(auth_info[curr_role].roleid);
557 foreach(mem, roles_list)
560 authmem_entry *found_mem;
565 key.memberid = lfirst_oid(mem);
566 found_mem = bsearch(&key, authmem_info, total_mem,
567 sizeof(authmem_entry), mem_compar);
572 * bsearch found a match for us; but if there were multiple
573 * matches it could have found any one of them. Locate first
576 first_found = last_found = (found_mem - authmem_info);
577 while (first_found > 0 &&
578 mem_compar(&key, &authmem_info[first_found - 1]) == 0)
580 while (last_found + 1 < total_mem &&
581 mem_compar(&key, &authmem_info[last_found + 1]) == 0)
585 * Now add all the new roles to roles_list.
587 for (i = first_found; i <= last_found; i++)
588 roles_list = list_append_unique_oid(roles_list,
589 authmem_info[i].roleid);
593 * Convert list of role Oids to list of role names. We must do
594 * this before re-sorting auth_info.
596 * We skip the first list element (curr_role itself) since there
597 * is no point in writing that a role is a member of itself.
599 for_each_cell(mem, lnext(list_head(roles_list)))
602 auth_entry *found_role;
604 key_auth.roleid = lfirst_oid(mem);
605 found_role = bsearch(&key_auth, auth_info, total_roles,
606 sizeof(auth_entry), oid_compar);
607 if (found_role) /* paranoia */
608 roles_names_list = lappend(roles_names_list,
609 found_role->rolname);
611 auth_info[curr_role].member_of = roles_names_list;
612 list_free(roles_list);
617 * Now sort auth_info into rolname order for output, and write the file.
619 qsort(auth_info, total_roles, sizeof(auth_entry), name_compar);
621 for (curr_role = 0; curr_role < total_roles; curr_role++)
623 auth_entry *arole = &auth_info[curr_role];
625 if (arole->rolcanlogin)
629 fputs_quote(arole->rolname, fp);
631 fputs_quote(arole->rolpassword, fp);
633 fputs_quote(arole->rolvaliduntil, fp);
635 foreach(mem, arole->member_of)
638 fputs_quote((char *) lfirst(mem), fp);
647 (errcode_for_file_access(),
648 errmsg("could not write to temporary file \"%s\": %m",
652 * Rename the temp file to its final name, deleting the old flat file. We
653 * expect that rename(2) is an atomic action.
655 if (rename(tempname, filename))
657 (errcode_for_file_access(),
658 errmsg("could not rename file \"%s\" to \"%s\": %m",
659 tempname, filename)));
664 * This routine is called once during database startup, after completing
665 * WAL replay if needed. Its purpose is to sync the flat files with the
666 * current state of the database tables. This is particularly important
667 * during PITR operation, since the flat files will come from the
668 * base backup which may be far out of sync with the current state.
670 * In theory we could skip rebuilding the flat files if no WAL replay
671 * occurred, but it seems best to just do it always. We have to
672 * scan pg_database to compute the XID wrap limit anyway. Also, this
673 * policy means we need not force initdb to change the format of the
676 * In a standalone backend we pass database_only = true to skip processing
677 * the auth file. We won't need it, and building it could fail if there's
678 * something corrupt in the authid/authmem catalogs.
681 BuildFlatFiles(bool database_only)
690 * We don't have any hope of running a real relcache, but we can use the
691 * same fake-relcache facility that WAL replay uses.
693 XLogInitRelationCache();
695 /* Need a resowner to keep the heapam and buffer code happy */
696 owner = ResourceOwnerCreate(NULL, "BuildFlatFiles");
697 CurrentResourceOwner = owner;
699 /* hard-wired path to pg_database */
700 rnode.spcNode = GLOBALTABLESPACE_OID;
702 rnode.relNode = DatabaseRelationId;
704 /* No locking is needed because no one else is alive yet */
705 rel_db = XLogOpenRelation(rnode);
706 write_database_file(rel_db);
710 /* hard-wired path to pg_authid */
711 rnode.spcNode = GLOBALTABLESPACE_OID;
713 rnode.relNode = AuthIdRelationId;
714 rel_authid = XLogOpenRelation(rnode);
716 /* hard-wired path to pg_auth_members */
717 rnode.spcNode = GLOBALTABLESPACE_OID;
719 rnode.relNode = AuthMemRelationId;
720 rel_authmem = XLogOpenRelation(rnode);
722 write_auth_file(rel_authid, rel_authmem);
725 CurrentResourceOwner = NULL;
726 ResourceOwnerDelete(owner);
728 XLogCloseRelationCache();
733 * This routine is called during transaction commit or abort.
735 * On commit, if we've written any of the critical database tables during
736 * the current transaction, update the flat files and signal the postmaster.
738 * On abort, just reset the static flags so we don't try to do it on the
739 * next successful commit.
741 * NB: this should be the last step before actual transaction commit.
742 * If any error aborts the transaction after we run this code, the postmaster
743 * will still have received and cached the changed data; so minimize the
744 * window for such problems.
747 AtEOXact_UpdateFlatFiles(bool isCommit)
749 Relation drel = NULL;
750 Relation arel = NULL;
751 Relation mrel = NULL;
753 if (database_file_update_subid == InvalidSubTransactionId &&
754 auth_file_update_subid == InvalidSubTransactionId)
755 return; /* nothing to do */
759 database_file_update_subid = InvalidSubTransactionId;
760 auth_file_update_subid = InvalidSubTransactionId;
765 * Advance command counter to be certain we see all effects of the current
768 CommandCounterIncrement();
771 * Open and lock the needed catalog(s).
773 * Even though we only need AccessShareLock, this could theoretically fail
774 * due to deadlock. In practice, however, our transaction already holds
775 * RowExclusiveLock or better (it couldn't have updated the catalog
776 * without such a lock). This implies that dbcommands.c and other places
777 * that force flat-file updates must not follow the common practice of
778 * dropping catalog locks before commit.
780 if (database_file_update_subid != InvalidSubTransactionId)
781 drel = heap_open(DatabaseRelationId, AccessShareLock);
783 if (auth_file_update_subid != InvalidSubTransactionId)
785 arel = heap_open(AuthIdRelationId, AccessShareLock);
786 mrel = heap_open(AuthMemRelationId, AccessShareLock);
790 * Obtain special locks to ensure that two transactions don't try to write
791 * the same flat file concurrently. Quite aside from any direct risks of
792 * corrupted output, the winning writer probably wouldn't have seen the
793 * other writer's updates. By taking a lock and holding it till commit,
794 * we ensure that whichever updater goes second will see the other
795 * updater's changes as committed, and thus the final state of the file
796 * will include all updates.
798 * We use a lock on "database 0" to protect writing the pg_database flat
799 * file, and a lock on "role 0" to protect the auth file. This is a bit
800 * ugly but it's not worth inventing any more-general convention. (Any
801 * two locktags that are never used for anything else would do.)
803 * This is safe against deadlock as long as these are the very last locks
804 * acquired during the transaction.
806 if (database_file_update_subid != InvalidSubTransactionId)
807 LockSharedObject(DatabaseRelationId, InvalidOid, 0,
808 AccessExclusiveLock);
810 if (auth_file_update_subid != InvalidSubTransactionId)
811 LockSharedObject(AuthIdRelationId, InvalidOid, 0,
812 AccessExclusiveLock);
814 /* Okay to write the files */
815 if (database_file_update_subid != InvalidSubTransactionId)
817 database_file_update_subid = InvalidSubTransactionId;
818 write_database_file(drel);
819 heap_close(drel, NoLock);
822 if (auth_file_update_subid != InvalidSubTransactionId)
824 auth_file_update_subid = InvalidSubTransactionId;
825 write_auth_file(arel, mrel);
826 heap_close(arel, NoLock);
827 heap_close(mrel, NoLock);
831 * Signal the postmaster to reload its caches.
833 SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);
838 * This routine is called during transaction prepare.
840 * Record which files need to be refreshed if this transaction later
843 * Note: it's OK to clear the flags immediately, since if the PREPARE fails
844 * further on, we'd only reset the flags anyway. So there's no need for a
845 * separate PostPrepare call.
848 AtPrepare_UpdateFlatFiles(void)
852 if (database_file_update_subid != InvalidSubTransactionId)
854 database_file_update_subid = InvalidSubTransactionId;
855 info |= FF_BIT_DATABASE;
857 if (auth_file_update_subid != InvalidSubTransactionId)
859 auth_file_update_subid = InvalidSubTransactionId;
863 RegisterTwoPhaseRecord(TWOPHASE_RM_FLATFILES_ID, info,
869 * AtEOSubXact_UpdateFlatFiles
871 * Called at subtransaction end, this routine resets or updates the
872 * need-to-update-files flags.
875 AtEOSubXact_UpdateFlatFiles(bool isCommit,
876 SubTransactionId mySubid,
877 SubTransactionId parentSubid)
881 if (database_file_update_subid == mySubid)
882 database_file_update_subid = parentSubid;
884 if (auth_file_update_subid == mySubid)
885 auth_file_update_subid = parentSubid;
889 if (database_file_update_subid == mySubid)
890 database_file_update_subid = InvalidSubTransactionId;
892 if (auth_file_update_subid == mySubid)
893 auth_file_update_subid = InvalidSubTransactionId;
899 * This trigger is fired whenever someone modifies pg_database, pg_authid
900 * or pg_auth_members via general-purpose INSERT/UPDATE/DELETE commands.
902 * It is sufficient for this to be a STATEMENT trigger since we don't
903 * care which individual rows changed. It doesn't much matter whether
904 * it's a BEFORE or AFTER trigger.
907 flatfile_update_trigger(PG_FUNCTION_ARGS)
909 TriggerData *trigdata = (TriggerData *) fcinfo->context;
911 if (!CALLED_AS_TRIGGER(fcinfo))
913 "flatfile_update_trigger was not called by trigger manager");
915 if (RelationGetNamespace(trigdata->tg_relation) != PG_CATALOG_NAMESPACE)
916 elog(ERROR, "flatfile_update_trigger was called for wrong table");
918 switch (RelationGetRelid(trigdata->tg_relation))
920 case DatabaseRelationId:
921 database_file_update_needed();
923 case AuthIdRelationId:
924 case AuthMemRelationId:
925 auth_file_update_needed();
928 elog(ERROR, "flatfile_update_trigger was called for wrong table");
932 return PointerGetDatum(NULL);
937 * 2PC processing routine for COMMIT PREPARED case.
939 * (We don't have to do anything for ROLLBACK PREPARED.)
942 flatfile_twophase_postcommit(TransactionId xid, uint16 info,
943 void *recdata, uint32 len)
946 * Set flags to do the needed file updates at the end of my own current
947 * transaction. (XXX this has some issues if my own transaction later
948 * rolls back, or if there is any significant delay before I commit. OK
949 * for now because we disallow COMMIT PREPARED inside a transaction
952 if (info & FF_BIT_DATABASE)
953 database_file_update_needed();
954 if (info & FF_BIT_AUTH)
955 auth_file_update_needed();