]> granicus.if.org Git - postgresql/blob - src/backend/utils/init/flatfiles.c
Flat file cleanup phase 2: make it work for pg_group. The flat group
[postgresql] / src / backend / utils / init / flatfiles.c
1 /*-------------------------------------------------------------------------
2  *
3  * flatfiles.c
4  *        Routines for maintaining "flat file" images of the shared catalogs.
5  *
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.
10  *
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.
20  *
21  *
22  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
23  * Portions Copyright (c) 1994, Regents of the University of California
24  *
25  * $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.2 2005/02/20 04:45:59 tgl Exp $
26  *
27  *-------------------------------------------------------------------------
28  */
29 #include "postgres.h"
30
31 #include <sys/stat.h>
32 #include <unistd.h>
33
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"
50
51
52 #define DATABASE_FLAT_FILE      "pg_database"
53 #define GROUP_FLAT_FILE         "pg_group"
54 #define USER_FLAT_FILE          "pg_pwd"
55
56
57 /*
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.
68  */
69 static SubTransactionId database_file_update_subid = InvalidSubTransactionId;
70 static SubTransactionId group_file_update_subid = InvalidSubTransactionId;
71 static SubTransactionId user_file_update_subid = InvalidSubTransactionId;
72
73
74 /*
75  * Mark flat database file as needing an update (because pg_database changed)
76  */
77 void
78 database_file_update_needed(void)
79 {
80         if (database_file_update_subid == InvalidSubTransactionId)
81                 database_file_update_subid = GetCurrentSubTransactionId();
82 }
83
84 /*
85  * Mark flat group file as needing an update (because pg_group changed)
86  */
87 void
88 group_file_update_needed(void)
89 {
90         if (group_file_update_subid == InvalidSubTransactionId)
91                 group_file_update_subid = GetCurrentSubTransactionId();
92 }
93
94 /*
95  * Mark flat user file as needing an update (because pg_shadow changed)
96  */
97 void
98 user_file_update_needed(void)
99 {
100         if (user_file_update_subid == InvalidSubTransactionId)
101                 user_file_update_subid = GetCurrentSubTransactionId();
102 }
103
104
105 /*
106  * database_getflatfilename --- get full pathname of database file
107  *
108  * Note that result string is palloc'd, and should be freed by the caller.
109  */
110 char *
111 database_getflatfilename(void)
112 {
113         int                     bufsize;
114         char       *pfnam;
115
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);
120
121         return pfnam;
122 }
123
124 /*
125  * group_getflatfilename --- get full pathname of group file
126  *
127  * Note that result string is palloc'd, and should be freed by the caller.
128  */
129 char *
130 group_getflatfilename(void)
131 {
132         int                     bufsize;
133         char       *pfnam;
134
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);
139
140         return pfnam;
141 }
142
143 /*
144  * Get full pathname of password file.
145  *
146  * Note that result string is palloc'd, and should be freed by the caller.
147  */
148 char *
149 user_getflatfilename(void)
150 {
151         int                     bufsize;
152         char       *pfnam;
153
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);
158
159         return pfnam;
160 }
161
162
163 /*
164  *      fputs_quote
165  *
166  *      Outputs string in quotes, with double-quotes duplicated.
167  *      We could use quote_ident(), but that expects a TEXT argument.
168  */
169 static void
170 fputs_quote(const char *str, FILE *fp)
171 {
172         fputc('"', fp);
173         while (*str)
174         {
175                 fputc(*str, fp);
176                 if (*str == '"')
177                         fputc('"', fp);
178                 str++;
179         }
180         fputc('"', fp);
181 }
182
183 /*
184  * name_okay
185  *
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.
188  */
189 static bool
190 name_okay(const char *str)
191 {
192         int                     i;
193
194         i = strcspn(str, "\r\n");
195         return (str[i] == '\0');
196 }
197
198
199 /*
200  * write_database_file: update the flat database file
201  *
202  * A side effect is to determine the oldest database's datfrozenxid
203  * so we can set or update the XID wrap limit.
204  */
205 static void
206 write_database_file(Relation drel)
207 {
208         char       *filename,
209                            *tempname;
210         int                     bufsize;
211         FILE       *fp;
212         mode_t          oumask;
213         HeapScanDesc scan;
214         HeapTuple       tuple;
215         NameData        oldest_datname;
216         TransactionId oldest_datfrozenxid = InvalidTransactionId;
217
218         /*
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.
222          */
223         filename = database_getflatfilename();
224         bufsize = strlen(filename) + 12;
225         tempname = (char *) palloc(bufsize);
226         snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
227
228         oumask = umask((mode_t) 077);
229         fp = AllocateFile(tempname, "w");
230         umask(oumask);
231         if (fp == NULL)
232                 ereport(ERROR,
233                                 (errcode_for_file_access(),
234                                  errmsg("could not write to temporary file \"%s\": %m",
235                                                 tempname)));
236
237         /*
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?)
241          */
242         scan = heap_beginscan(drel, SnapshotSelf, 0, NULL);
243         while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
244         {
245                 Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
246                 char       *datname;
247                 Oid                     datoid;
248                 TransactionId datfrozenxid;
249
250                 datname = NameStr(dbform->datname);
251                 datoid = HeapTupleGetOid(tuple);
252                 datfrozenxid = dbform->datfrozenxid;
253
254                 /*
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.
258                  */
259                 if (dbform->datallowconn &&
260                         TransactionIdIsNormal(datfrozenxid))
261                 {
262                         if (oldest_datfrozenxid == InvalidTransactionId ||
263                                 TransactionIdPrecedes(datfrozenxid, oldest_datfrozenxid))
264                         {
265                                 oldest_datfrozenxid = datfrozenxid;
266                                 namestrcpy(&oldest_datname, datname);
267                         }
268                 }
269
270                 /*
271                  * Check for illegal characters in the database name.
272                  */
273                 if (!name_okay(datname))
274                 {
275                         ereport(LOG,
276                                         (errmsg("invalid database name \"%s\"", datname)));
277                         continue;
278                 }
279
280                 /*
281                  * The file format is: "dbname" oid frozenxid
282                  *
283                  * The xid is not needed for backend startup, but may be of use
284                  * for forensic purposes.
285                  */
286                 fputs_quote(datname, fp);
287                 fprintf(fp, " %u %u\n", datoid, datfrozenxid);
288         }
289         heap_endscan(scan);
290
291         if (FreeFile(fp))
292                 ereport(ERROR,
293                                 (errcode_for_file_access(),
294                                  errmsg("could not write to temporary file \"%s\": %m",
295                                                 tempname)));
296
297         /*
298          * Rename the temp file to its final name, deleting the old flat file.
299          * We expect that rename(2) is an atomic action.
300          */
301         if (rename(tempname, filename))
302                 ereport(ERROR,
303                                 (errcode_for_file_access(),
304                                  errmsg("could not rename file \"%s\" to \"%s\": %m",
305                                                 tempname, filename)));
306
307         pfree(tempname);
308         pfree(filename);
309
310         /*
311          * Set the transaction ID wrap limit using the oldest datfrozenxid
312          */
313         if (oldest_datfrozenxid != InvalidTransactionId)
314                 SetTransactionIdLimit(oldest_datfrozenxid, &oldest_datname);
315 }
316
317
318 /*
319  * write_group_file: update the flat group file
320  */
321 static void
322 write_group_file(Relation grel)
323 {
324         char       *filename,
325                            *tempname;
326         int                     bufsize;
327         FILE       *fp;
328         mode_t          oumask;
329         HeapScanDesc scan;
330         HeapTuple       tuple;
331
332         /*
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.
336          */
337         filename = group_getflatfilename();
338         bufsize = strlen(filename) + 12;
339         tempname = (char *) palloc(bufsize);
340         snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
341
342         oumask = umask((mode_t) 077);
343         fp = AllocateFile(tempname, "w");
344         umask(oumask);
345         if (fp == NULL)
346                 ereport(ERROR,
347                                 (errcode_for_file_access(),
348                                  errmsg("could not write to temporary file \"%s\": %m",
349                                                 tempname)));
350
351         /*
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?)
355          */
356         scan = heap_beginscan(grel, SnapshotSelf, 0, NULL);
357         while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
358         {
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 */
364                 Datum           datum;
365                 char       *groname;
366                 IdList     *grolist_p;
367                 AclId      *aidp;
368                 int                     i,
369                                         num;
370
371                 groname = NameStr(grpform->groname);
372
373                 /*
374                  * Check for illegal characters in the group name.
375                  */
376                 if (!name_okay(groname))
377                 {
378                         ereport(LOG,
379                                         (errmsg("invalid group name \"%s\"", groname)));
380                         continue;
381                 }
382
383                 /*
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.
388                  */
389                 tp = (char *) tup + tup->t_hoff;
390                 off = offsetof(FormData_pg_group, grolist);
391
392                 if (HeapTupleHasNulls(tuple) &&
393                         att_isnull(Anum_pg_group_grolist - 1, bp))
394                 {
395                         /* grolist is null, so we can ignore this group */
396                         continue;
397                 }
398
399                 /* assume grolist is pass-by-ref */
400                 datum = PointerGetDatum(tp + off);
401
402                 /*
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.
407                  *
408                  * Detect startup mode by noting whether we got a tupdesc.
409                  */
410                 if (VARATT_IS_EXTERNAL(DatumGetPointer(datum)) &&
411                         RelationGetDescr(grel) == NULL)
412                         continue;
413
414                 /* be sure the IdList is not toasted */
415                 grolist_p = DatumGetIdListP(datum);
416
417                 /*
418                  * The file format is: "groupname"    usesysid1 usesysid2 ...
419                  *
420                  * We ignore groups that have no members.
421                  */
422                 aidp = IDLIST_DAT(grolist_p);
423                 num = IDLIST_NUM(grolist_p);
424                 if (num > 0)
425                 {
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]);
430                         fputs("\n", fp);
431                 }
432
433                 /* if IdList was toasted, free detoasted copy */
434                 if ((Pointer) grolist_p != DatumGetPointer(datum))
435                         pfree(grolist_p);
436         }
437         heap_endscan(scan);
438
439         if (FreeFile(fp))
440                 ereport(ERROR,
441                                 (errcode_for_file_access(),
442                                  errmsg("could not write to temporary file \"%s\": %m",
443                                                 tempname)));
444
445         /*
446          * Rename the temp file to its final name, deleting the old flat file.
447          * We expect that rename(2) is an atomic action.
448          */
449         if (rename(tempname, filename))
450                 ereport(ERROR,
451                                 (errcode_for_file_access(),
452                                  errmsg("could not rename file \"%s\" to \"%s\": %m",
453                                                 tempname, filename)));
454
455         pfree(tempname);
456         pfree(filename);
457 }
458
459
460 /*
461  * write_user_file: update the flat password file
462  */
463 static void
464 write_user_file(Relation urel)
465 {
466         char       *filename,
467                            *tempname;
468         int                     bufsize;
469         FILE       *fp;
470         mode_t          oumask;
471         HeapScanDesc scan;
472         HeapTuple       tuple;
473
474         /*
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.
478          */
479         filename = user_getflatfilename();
480         bufsize = strlen(filename) + 12;
481         tempname = (char *) palloc(bufsize);
482         snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
483
484         oumask = umask((mode_t) 077);
485         fp = AllocateFile(tempname, "w");
486         umask(oumask);
487         if (fp == NULL)
488                 ereport(ERROR,
489                                 (errcode_for_file_access(),
490                                  errmsg("could not write to temporary file \"%s\": %m",
491                                                 tempname)));
492
493         /*
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?)
497          */
498         scan = heap_beginscan(urel, SnapshotSelf, 0, NULL);
499         while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
500         {
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 */
506                 Datum           datum;
507                 char       *usename,
508                                    *passwd,
509                                    *valuntil;
510                 AclId           usesysid;
511
512                 usename = NameStr(pwform->usename);
513                 usesysid = pwform->usesysid;
514
515                 /*
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.
520                  */
521                 tp = (char *) tup + tup->t_hoff;
522                 off = offsetof(FormData_pg_shadow, passwd);
523
524                 if (HeapTupleHasNulls(tuple) &&
525                         att_isnull(Anum_pg_shadow_passwd - 1, bp))
526                 {
527                         /* passwd is null, emit as an empty string */
528                         passwd = pstrdup("");
529                 }
530                 else
531                 {
532                         /* assume passwd is pass-by-ref */
533                         datum = PointerGetDatum(tp + off);
534
535                         /*
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.
538                          */
539                         if (VARATT_IS_EXTERNAL(DatumGetPointer(datum)))
540                                 passwd = pstrdup("");
541                         else
542                                 passwd = DatumGetCString(DirectFunctionCall1(textout, datum));
543
544                         /* assume passwd has attlen -1 */
545                         off = att_addlength(off, -1, tp + off);
546                 }
547
548                 if (HeapTupleHasNulls(tuple) &&
549                         att_isnull(Anum_pg_shadow_valuntil - 1, bp))
550                 {
551                         /* valuntil is null, emit as an empty string */
552                         valuntil = pstrdup("");
553                 }
554                 else
555                 {
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));
561                 }
562
563                 /*
564                  * Check for illegal characters in the user name and password.
565                  */
566                 if (!name_okay(usename))
567                 {
568                         ereport(LOG,
569                                         (errmsg("invalid user name \"%s\"", usename)));
570                         continue;
571                 }
572                 if (!name_okay(passwd))
573                 {
574                         ereport(LOG,
575                                         (errmsg("invalid user password \"%s\"", passwd)));
576                         continue;
577                 }
578
579                 /*
580                  * The file format is: "usename" usesysid "passwd" "valuntil"
581                  */
582                 fputs_quote(usename, fp);
583                 fprintf(fp, " %u ", usesysid);
584                 fputs_quote(passwd, fp);
585                 fputs(" ", fp);
586                 fputs_quote(valuntil, fp);
587                 fputs("\n", fp);
588
589                 pfree(passwd);
590                 pfree(valuntil);
591         }
592         heap_endscan(scan);
593
594         if (FreeFile(fp))
595                 ereport(ERROR,
596                                 (errcode_for_file_access(),
597                                  errmsg("could not write to temporary file \"%s\": %m",
598                                                 tempname)));
599
600         /*
601          * Rename the temp file to its final name, deleting the old flat file.
602          * We expect that rename(2) is an atomic action.
603          */
604         if (rename(tempname, filename))
605                 ereport(ERROR,
606                                 (errcode_for_file_access(),
607                                  errmsg("could not rename file \"%s\" to \"%s\": %m",
608                                                 tempname, filename)));
609
610         pfree(tempname);
611         pfree(filename);
612 }
613
614
615 /*
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.
621  *
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.
625  *
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.
629  */
630 void
631 BuildFlatFiles(bool database_only)
632 {
633         ResourceOwner owner;
634         RelFileNode rnode;
635         Relation        rel;
636
637         /*
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.
640          */
641         XLogInitRelationCache();
642
643         /* Need a resowner to keep the heapam and buffer code happy */
644         owner = ResourceOwnerCreate(NULL, "BuildFlatFiles");
645         CurrentResourceOwner = owner;
646
647         /* hard-wired path to pg_database */
648         rnode.spcNode = GLOBALTABLESPACE_OID;
649         rnode.dbNode = 0;
650         rnode.relNode = RelOid_pg_database;
651
652         /* No locking is needed because no one else is alive yet */
653         rel = XLogOpenRelation(true, 0, rnode);
654         write_database_file(rel);
655
656         if (!database_only)
657         {
658                 /* hard-wired path to pg_group */
659                 rnode.spcNode = GLOBALTABLESPACE_OID;
660                 rnode.dbNode = 0;
661                 rnode.relNode = RelOid_pg_group;
662
663                 rel = XLogOpenRelation(true, 0, rnode);
664                 write_group_file(rel);
665
666                 /* hard-wired path to pg_shadow */
667                 rnode.spcNode = GLOBALTABLESPACE_OID;
668                 rnode.dbNode = 0;
669                 rnode.relNode = RelOid_pg_shadow;
670
671                 rel = XLogOpenRelation(true, 0, rnode);
672                 write_user_file(rel);
673         }
674
675         CurrentResourceOwner = NULL;
676         ResourceOwnerDelete(owner);
677
678         XLogCloseRelationCache();
679 }
680
681
682 /*
683  * This routine is called during transaction commit or abort.
684  *
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.
687  *
688  * On abort, just reset the static flags so we don't try to do it on the
689  * next successful commit.
690  *
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.
695  */
696 void
697 AtEOXact_UpdateFlatFiles(bool isCommit)
698 {
699         Relation        drel = NULL;
700         Relation        grel = NULL;
701         Relation        urel = NULL;
702
703         if (database_file_update_subid == InvalidSubTransactionId &&
704                 group_file_update_subid == InvalidSubTransactionId &&
705                 user_file_update_subid == InvalidSubTransactionId)
706                 return;                                 /* nothing to do */
707
708         if (!isCommit)
709         {
710                 database_file_update_subid = InvalidSubTransactionId;
711                 group_file_update_subid = InvalidSubTransactionId;
712                 user_file_update_subid = InvalidSubTransactionId;
713                 return;
714         }
715
716         /*
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.
723          */
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);
730
731         /* Okay to write the files */
732         if (database_file_update_subid != InvalidSubTransactionId)
733         {
734                 database_file_update_subid = InvalidSubTransactionId;
735                 write_database_file(drel);
736                 heap_close(drel, NoLock);
737         }
738
739         if (group_file_update_subid != InvalidSubTransactionId)
740         {
741                 group_file_update_subid = InvalidSubTransactionId;
742                 write_group_file(grel);
743                 heap_close(grel, NoLock);
744         }
745
746         if (user_file_update_subid != InvalidSubTransactionId)
747         {
748                 user_file_update_subid = InvalidSubTransactionId;
749                 write_user_file(urel);
750                 heap_close(urel, NoLock);
751         }
752
753         /*
754          * Signal the postmaster to reload its caches.
755          */
756         SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);
757 }
758
759 /*
760  * AtEOSubXact_UpdateFlatFiles
761  *
762  * Called at subtransaction end, this routine resets or updates the
763  * need-to-update-files flags.
764  */
765 void
766 AtEOSubXact_UpdateFlatFiles(bool isCommit,
767                                                         SubTransactionId mySubid,
768                                                         SubTransactionId parentSubid)
769 {
770         if (isCommit)
771         {
772                 if (database_file_update_subid == mySubid)
773                         database_file_update_subid = parentSubid;
774
775                 if (group_file_update_subid == mySubid)
776                         group_file_update_subid = parentSubid;
777
778                 if (user_file_update_subid == mySubid)
779                         user_file_update_subid = parentSubid;
780         }
781         else
782         {
783                 if (database_file_update_subid == mySubid)
784                         database_file_update_subid = InvalidSubTransactionId;
785
786                 if (group_file_update_subid == mySubid)
787                         group_file_update_subid = InvalidSubTransactionId;
788
789                 if (user_file_update_subid == mySubid)
790                         user_file_update_subid = InvalidSubTransactionId;
791         }
792 }
793
794
795 /*
796  * This trigger is fired whenever someone modifies pg_database, pg_shadow
797  * or pg_group via general-purpose INSERT/UPDATE/DELETE commands.
798  *
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.
802  */
803 Datum
804 flatfile_update_trigger(PG_FUNCTION_ARGS)
805 {
806         TriggerData *trigdata = (TriggerData *) fcinfo->context;
807
808         if (!CALLED_AS_TRIGGER(fcinfo))
809                 elog(ERROR,
810                          "flatfile_update_trigger was not called by trigger manager");
811
812         if (RelationGetNamespace(trigdata->tg_relation) != PG_CATALOG_NAMESPACE)
813                 elog(ERROR, "flatfile_update_trigger was called for wrong table");
814
815         switch (RelationGetRelid(trigdata->tg_relation))
816         {
817                 case RelOid_pg_database:
818                         database_file_update_needed();
819                         break;
820                 case RelOid_pg_group:
821                         group_file_update_needed();
822                         break;
823                 case RelOid_pg_shadow:
824                         user_file_update_needed();
825                         break;
826                 default:
827                         elog(ERROR, "flatfile_update_trigger was called for wrong table");
828                         break;
829         }
830
831         return PointerGetDatum(NULL);
832 }
833
834
835 /*
836  * Old version of trigger --- remove after we can force an initdb
837  */
838 extern Datum update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS);
839
840 Datum
841 update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS)
842 {
843         return flatfile_update_trigger(fcinfo);
844 }