]> granicus.if.org Git - postgresql/blob - src/backend/utils/init/miscinit.c
Check for malloc failure.
[postgresql] / src / backend / utils / init / miscinit.c
1 /*-------------------------------------------------------------------------
2  *
3  * miscinit.c
4  *        miscellaneous initialization support stuff
5  *
6  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/src/backend/utils/init/miscinit.c,v 1.68 2001/06/01 20:27:41 tgl Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16
17 #include <sys/param.h>
18 #include <sys/types.h>
19 #include <signal.h>
20 #include <sys/stat.h>
21 #include <sys/file.h>
22 #include <fcntl.h>
23 #include <unistd.h>
24 #include <grp.h>
25 #include <pwd.h>
26 #include <stdlib.h>
27 #include <errno.h>
28
29 #include "catalog/catname.h"
30 #include "catalog/pg_shadow.h"
31 #include "miscadmin.h"
32 #include "utils/builtins.h"
33 #include "utils/syscache.h"
34
35
36 #ifdef CYR_RECODE
37 unsigned char RecodeForwTable[128];
38 unsigned char RecodeBackTable[128];
39
40 #endif
41
42 ProcessingMode Mode = InitProcessing;
43
44 /* Note: we rely on these to initialize as zeroes */
45 static char directoryLockFile[MAXPGPATH];
46 static char socketLockFile[MAXPGPATH];
47
48
49 /* ----------------------------------------------------------------
50  *              ignoring system indexes support stuff
51  * ----------------------------------------------------------------
52  */
53
54 static bool isIgnoringSystemIndexes = false;
55
56 /*
57  * IsIgnoringSystemIndexes
58  *              True if ignoring system indexes.
59  */
60 bool
61 IsIgnoringSystemIndexes()
62 {
63         return isIgnoringSystemIndexes;
64 }
65
66 /*
67  * IgnoreSystemIndexes
68  *      Set true or false whether PostgreSQL ignores system indexes.
69  *
70  */
71 void
72 IgnoreSystemIndexes(bool mode)
73 {
74         isIgnoringSystemIndexes = mode;
75 }
76
77 /* ----------------------------------------------------------------
78  *                              database path / name support stuff
79  * ----------------------------------------------------------------
80  */
81
82 void
83 SetDatabasePath(const char *path)
84 {
85         free(DatabasePath);
86         /* use strdup since this is done before memory contexts are set up */
87         if (path)
88         {
89                 DatabasePath = strdup(path);
90                 AssertState(DatabasePath);
91         }
92 }
93
94 void
95 SetDatabaseName(const char *name)
96 {
97         free(DatabaseName);
98         if (name)
99         {
100                 DatabaseName = strdup(name);
101                 AssertState(DatabaseName);
102         }
103 }
104
105 /*
106  * Set data directory, but make sure it's an absolute path.  Use this,
107  * never set DataDir directly.
108  */
109 void
110 SetDataDir(const char *dir)
111 {
112         char       *new;
113
114         AssertArg(dir);
115         if (DataDir)
116                 free(DataDir);
117
118         if (dir[0] != '/')
119         {
120                 char       *buf;
121                 size_t          buflen;
122
123                 buflen = MAXPGPATH;
124                 for (;;)
125                 {
126                         buf = malloc(buflen);
127                         if (!buf)
128                                 elog(FATAL, "out of memory");
129
130                         if (getcwd(buf, buflen))
131                                 break;
132                         else if (errno == ERANGE)
133                         {
134                                 free(buf);
135                                 buflen *= 2;
136                                 continue;
137                         }
138                         else
139                         {
140                                 free(buf);
141                                 elog(FATAL, "cannot get current working directory: %m");
142                         }
143                 }
144
145                 new = malloc(strlen(buf) + 1 + strlen(dir) + 1);
146                 if (!new)
147                         elog(FATAL, "out of memory");
148                 sprintf(new, "%s/%s", buf, dir);
149                 free(buf);
150         }
151         else
152         {
153                 new = strdup(dir);
154                 if (!new)
155                         elog(FATAL, "out of memory");
156         }
157
158         DataDir = new;
159 }
160
161
162 /* ----------------------------------------------------------------
163  *                              MULTIBYTE stub code
164  *
165  * Even if MULTIBYTE is not enabled, these functions are necessary
166  * since pg_proc.h has references to them.
167  * ----------------------------------------------------------------
168  */
169
170 #ifndef MULTIBYTE
171
172 Datum
173 getdatabaseencoding(PG_FUNCTION_ARGS)
174 {
175         return DirectFunctionCall1(namein, CStringGetDatum("SQL_ASCII"));
176 }
177
178 Datum
179 PG_encoding_to_char(PG_FUNCTION_ARGS)
180 {
181         return DirectFunctionCall1(namein, CStringGetDatum("SQL_ASCII"));
182 }
183
184 Datum
185 PG_char_to_encoding(PG_FUNCTION_ARGS)
186 {
187         PG_RETURN_INT32(0);
188 }
189
190 #endif
191
192 /* ----------------------------------------------------------------
193  *                              CYR_RECODE support
194  * ----------------------------------------------------------------
195  */
196
197 #ifdef CYR_RECODE
198
199 #define MAX_TOKEN       80
200
201 /* Some standard C libraries, including GNU, have an isblank() function.
202    Others, including Solaris, do not.  So we have our own.
203 */
204 static bool
205 isblank(const char c)
206 {
207         return c == ' ' || c == 9 /* tab */ ;
208 }
209
210 static void
211 next_token(FILE *fp, char *buf, const int bufsz)
212 {
213 /*--------------------------------------------------------------------------
214   Grab one token out of fp.  Tokens are strings of non-blank
215   characters bounded by blank characters, beginning of line, and end
216   of line.      Blank means space or tab.  Return the token as *buf.
217   Leave file positioned to character immediately after the token or
218   EOF, whichever comes first.  If no more tokens on line, return null
219   string as *buf and position file to beginning of next line or EOF,
220   whichever comes first.
221 --------------------------------------------------------------------------*/
222         int                     c;
223         char       *eb = buf + (bufsz - 1);
224
225         /* Move over inital token-delimiting blanks */
226         while (isblank(c = getc(fp)));
227
228         if (c != '\n')
229         {
230
231                 /*
232                  * build a token in buf of next characters up to EOF, eol, or
233                  * blank.
234                  */
235                 while (c != EOF && c != '\n' && !isblank(c))
236                 {
237                         if (buf < eb)
238                                 *buf++ = c;
239                         c = getc(fp);
240
241                         /*
242                          * Put back the char right after the token (putting back EOF
243                          * is ok)
244                          */
245                 }
246                 ungetc(c, fp);
247         }
248         *buf = '\0';
249 }
250
251 static void
252 read_through_eol(FILE *file)
253 {
254         int                     c;
255
256         do
257                 c = getc(file);
258         while (c != '\n' && c != EOF);
259 }
260
261 void
262 SetCharSet()
263 {
264         FILE       *file;
265         char       *p,
266                                 c,
267                                 eof = false;
268         char       *map_file;
269         char            buf[MAX_TOKEN];
270         int                     i;
271         unsigned char FromChar,
272                                 ToChar;
273
274         for (i = 0; i < 128; i++)
275         {
276                 RecodeForwTable[i] = i + 128;
277                 RecodeBackTable[i] = i + 128;
278         }
279
280         p = getenv("PG_RECODETABLE");
281         if (p && *p != '\0')
282         {
283                 map_file = (char *) malloc((strlen(DataDir) +
284                                                                         strlen(p) + 2) * sizeof(char));
285                 if (! map_file)
286                         elog(FATAL, "out of memory");
287                 sprintf(map_file, "%s/%s", DataDir, p);
288                 file = AllocateFile(map_file, PG_BINARY_R);
289                 if (file == NULL)
290                 {
291                         free(map_file);
292                         return;
293                 }
294                 eof = false;
295                 while (!eof)
296                 {
297                         c = getc(file);
298                         ungetc(c, file);
299                         if (c == EOF)
300                                 eof = true;
301                         else
302                         {
303                                 if (c == '#')
304                                         read_through_eol(file);
305                                 else
306                                 {
307                                         /* Read the FromChar */
308                                         next_token(file, buf, sizeof(buf));
309                                         if (buf[0] != '\0')
310                                         {
311                                                 FromChar = strtoul(buf, 0, 0);
312                                                 /* Read the ToChar */
313                                                 next_token(file, buf, sizeof(buf));
314                                                 if (buf[0] != '\0')
315                                                 {
316                                                         ToChar = strtoul(buf, 0, 0);
317                                                         RecodeForwTable[FromChar - 128] = ToChar;
318                                                         RecodeBackTable[ToChar - 128] = FromChar;
319                                                 }
320                                                 read_through_eol(file);
321                                         }
322                                 }
323                         }
324                 }
325                 FreeFile(file);
326                 free(map_file);
327         }
328 }
329
330 char *
331 convertstr(unsigned char *buff, int len, int dest)
332 {
333         int                     i;
334         char       *ch = buff;
335
336         for (i = 0; i < len; i++, buff++)
337         {
338                 if (*buff > 127)
339                 {
340                         if (dest)
341                                 *buff = RecodeForwTable[*buff - 128];
342                         else
343                                 *buff = RecodeBackTable[*buff - 128];
344                 }
345         }
346         return ch;
347 }
348
349 #endif
350
351
352
353 /* ----------------------------------------------------------------
354  *      User ID things
355  *
356  * The session user is determined at connection start and never
357  * changes.  The current user may change when "setuid" functions
358  * are implemented.  Conceptually there is a stack, whose bottom
359  * is the session user.  You are yourself responsible to save and
360  * restore the current user id if you need to change it.
361  * ----------------------------------------------------------------
362  */
363 static Oid      CurrentUserId = InvalidOid;
364 static Oid      SessionUserId = InvalidOid;
365
366 static bool AuthenticatedUserIsSuperuser = false;
367
368 /*
369  * This function is relevant for all privilege checks.
370  */
371 Oid
372 GetUserId(void)
373 {
374         AssertState(OidIsValid(CurrentUserId));
375         return CurrentUserId;
376 }
377
378
379 void
380 SetUserId(Oid newid)
381 {
382         AssertArg(OidIsValid(newid));
383         CurrentUserId = newid;
384 }
385
386
387 /*
388  * This value is only relevant for informational purposes.
389  */
390 Oid
391 GetSessionUserId(void)
392 {
393         AssertState(OidIsValid(SessionUserId));
394         return SessionUserId;
395 }
396
397
398 void
399 SetSessionUserId(Oid newid)
400 {
401         AssertArg(OidIsValid(newid));
402         SessionUserId = newid;
403         /* Current user defaults to session user. */
404         if (!OidIsValid(CurrentUserId))
405                 CurrentUserId = newid;
406 }
407
408
409 void
410 InitializeSessionUserId(const char *username)
411 {
412         HeapTuple       userTup;
413
414         /*
415          * Don't do scans if we're bootstrapping, none of the system catalogs
416          * exist yet, and they should be owned by postgres anyway.
417          */
418         AssertState(!IsBootstrapProcessingMode());
419
420         /* call only once */
421         AssertState(!OidIsValid(SessionUserId));
422
423         userTup = SearchSysCache(SHADOWNAME,
424                                                          PointerGetDatum(username),
425                                                          0, 0, 0);
426         if (!HeapTupleIsValid(userTup))
427                 elog(FATAL, "user \"%s\" does not exist", username);
428
429         SetSessionUserId(((Form_pg_shadow) GETSTRUCT(userTup))->usesysid);
430
431         AuthenticatedUserIsSuperuser = ((Form_pg_shadow) GETSTRUCT(userTup))->usesuper;
432
433         ReleaseSysCache(userTup);
434 }
435
436
437
438 void SetSessionAuthorization(const char * username)
439 {
440         HeapTuple       userTup;
441
442         if (!AuthenticatedUserIsSuperuser)
443                 elog(ERROR, "permission denied");
444
445         userTup = SearchSysCache(SHADOWNAME,
446                                                          PointerGetDatum(username),
447                                                          0, 0, 0);
448         if (!HeapTupleIsValid(userTup))
449                 elog(ERROR, "user \"%s\" does not exist", username);
450
451         SetSessionUserId(((Form_pg_shadow) GETSTRUCT(userTup))->usesysid);
452         SetUserId(((Form_pg_shadow) GETSTRUCT(userTup))->usesysid);
453
454         ReleaseSysCache(userTup);
455 }
456
457
458 /*
459  * Get user name from user id
460  */
461 char *
462 GetUserName(Oid userid)
463 {
464         HeapTuple       tuple;
465         char       *result;
466
467         tuple = SearchSysCache(SHADOWSYSID,
468                                                    ObjectIdGetDatum(userid),
469                                                    0, 0, 0);
470         if (!HeapTupleIsValid(tuple))
471                 elog(ERROR, "invalid user id %u", (unsigned) userid);
472
473         result = pstrdup(NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename));
474
475         ReleaseSysCache(tuple);
476         return result;
477 }
478
479
480
481 /*-------------------------------------------------------------------------
482  *                              Interlock-file support
483  *
484  * These routines are used to create both a data-directory lockfile
485  * ($DATADIR/postmaster.pid) and a Unix-socket-file lockfile ($SOCKFILE.lock).
486  * Both kinds of files contain the same info:
487  *
488  *              Owning process' PID
489  *              Data directory path
490  *
491  * By convention, the owning process' PID is negated if it is a standalone
492  * backend rather than a postmaster.  This is just for informational purposes.
493  * The path is also just for informational purposes (so that a socket lockfile
494  * can be more easily traced to the associated postmaster).
495  *
496  * A data-directory lockfile can optionally contain a third line, containing
497  * the key and ID for the shared memory block used by this postmaster.
498  *
499  * On successful lockfile creation, a proc_exit callback to remove the
500  * lockfile is automatically created.
501  *-------------------------------------------------------------------------
502  */
503
504 /*
505  * proc_exit callback to remove a lockfile.
506  */
507 static void
508 UnlinkLockFile(int status, Datum filename)
509 {
510         unlink((char *) DatumGetPointer(filename));
511         /* Should we complain if the unlink fails? */
512 }
513
514 /*
515  * Create a lockfile, if possible
516  *
517  * Call CreateLockFile with the name of the lockfile to be created.
518  * Returns true if successful, false if not (with a message on stderr).
519  *
520  * amPostmaster is used to determine how to encode the output PID.
521  * isDDLock and refName are used to determine what error message to produce.
522  */
523 static bool
524 CreateLockFile(const char *filename, bool amPostmaster,
525                            bool isDDLock, const char *refName)
526 {
527         int                     fd;
528         char            buffer[MAXPGPATH + 100];
529         int                     len;
530         int                     encoded_pid;
531         pid_t           other_pid;
532         pid_t           my_pid = getpid();
533
534         /*
535          * We need a loop here because of race conditions.
536          */
537         for (;;)
538         {
539
540                 /*
541                  * Try to create the lock file --- O_EXCL makes this atomic.
542                  */
543                 fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
544                 if (fd >= 0)
545                         break;                          /* Success; exit the retry loop */
546
547                 /*
548                  * Couldn't create the pid file. Probably it already exists.
549                  */
550                 if (errno != EEXIST && errno != EACCES)
551                         elog(FATAL, "Can't create lock file %s: %m", filename);
552
553                 /*
554                  * Read the file to get the old owner's PID.  Note race condition
555                  * here: file might have been deleted since we tried to create it.
556                  */
557                 fd = open(filename, O_RDONLY, 0600);
558                 if (fd < 0)
559                 {
560                         if (errno == ENOENT)
561                                 continue;               /* race condition; try again */
562                         elog(FATAL, "Can't read lock file %s: %m", filename);
563                 }
564                 if ((len = read(fd, buffer, sizeof(buffer) - 1)) <= 0)
565                         elog(FATAL, "Can't read lock file %s: %m", filename);
566                 close(fd);
567
568                 buffer[len] = '\0';
569                 encoded_pid = atoi(buffer);
570
571                 /* if pid < 0, the pid is for postgres, not postmaster */
572                 other_pid = (pid_t) (encoded_pid < 0 ? -encoded_pid : encoded_pid);
573
574                 if (other_pid <= 0)
575                         elog(FATAL, "Bogus data in lock file %s", filename);
576
577                 /*
578                  * Check to see if the other process still exists
579                  *
580                  * Normally kill() will fail with ESRCH if the given PID doesn't
581                  * exist.  BeOS returns EINVAL for some silly reason, however.
582                  */
583                 if (other_pid != my_pid)
584                 {
585                         if (kill(other_pid, 0) == 0 ||
586                                 (errno != ESRCH
587 #ifdef __BEOS__
588                                  && errno != EINVAL
589 #endif
590                                  ))
591                         {
592                                 /* lockfile belongs to a live process */
593                                 fprintf(stderr, "Lock file \"%s\" already exists.\n",
594                                                 filename);
595                                 if (isDDLock)
596                                         fprintf(stderr,
597                                                         "Is another %s (pid %d) running in \"%s\"?\n",
598                                                         (encoded_pid < 0 ? "postgres" : "postmaster"),
599                                                         (int) other_pid, refName);
600                                 else
601                                         fprintf(stderr,
602                                                         "Is another %s (pid %d) using \"%s\"?\n",
603                                                         (encoded_pid < 0 ? "postgres" : "postmaster"),
604                                                         (int) other_pid, refName);
605                                 return false;
606                         }
607                 }
608
609                 /*
610                  * No, the creating process did not exist.      However, it could be
611                  * that the postmaster crashed (or more likely was kill -9'd by a
612                  * clueless admin) but has left orphan backends behind.  Check for
613                  * this by looking to see if there is an associated shmem segment
614                  * that is still in use.
615                  */
616                 if (isDDLock)
617                 {
618                         char       *ptr;
619                         unsigned long shmKey,
620                                                 shmId;
621
622                         ptr = strchr(buffer, '\n');
623                         if (ptr != NULL &&
624                                 (ptr = strchr(ptr + 1, '\n')) != NULL)
625                         {
626                                 ptr++;
627                                 if (sscanf(ptr, "%lu %lu", &shmKey, &shmId) == 2)
628                                 {
629                                         if (SharedMemoryIsInUse((IpcMemoryKey) shmKey,
630                                                                                         (IpcMemoryId) shmId))
631                                         {
632                                                 fprintf(stderr,
633                                                                 "Found a pre-existing shared memory block (ID %d) still in use.\n"
634                                                                 "If you're sure there are no old backends still running,\n"
635                                                                 "remove the shared memory block with ipcrm(1), or just\n"
636                                                                 "delete \"%s\".\n",
637                                                                 (int) shmId, filename);
638                                                 return false;
639                                         }
640                                 }
641                         }
642                 }
643
644                 /*
645                  * Looks like nobody's home.  Unlink the file and try again to
646                  * create it.  Need a loop because of possible race condition
647                  * against other would-be creators.
648                  */
649                 if (unlink(filename) < 0)
650                         elog(FATAL, "Can't remove old lock file %s: %m"
651                                  "\n\tThe file seems accidentally left, but I couldn't remove it."
652                                  "\n\tPlease remove the file by hand and try again.",
653                                  filename);
654         }
655
656         /*
657          * Successfully created the file, now fill it.
658          */
659         snprintf(buffer, sizeof(buffer), "%d\n%s\n",
660                          amPostmaster ? (int) my_pid : -((int) my_pid),
661                          DataDir);
662         if (write(fd, buffer, strlen(buffer)) != strlen(buffer))
663         {
664                 int                     save_errno = errno;
665
666                 close(fd);
667                 unlink(filename);
668                 errno = save_errno;
669                 elog(FATAL, "Can't write lock file %s: %m", filename);
670         }
671         close(fd);
672
673         /*
674          * Arrange for automatic removal of lockfile at proc_exit.
675          */
676         on_proc_exit(UnlinkLockFile, PointerGetDatum(strdup(filename)));
677
678         return true;                            /* Success! */
679 }
680
681 bool
682 CreateDataDirLockFile(const char *datadir, bool amPostmaster)
683 {
684         char            lockfile[MAXPGPATH];
685
686         snprintf(lockfile, sizeof(lockfile), "%s/postmaster.pid", datadir);
687         if (!CreateLockFile(lockfile, amPostmaster, true, datadir))
688                 return false;
689         /* Save name of lockfile for RecordSharedMemoryInLockFile */
690         strcpy(directoryLockFile, lockfile);
691         return true;
692 }
693
694 bool
695 CreateSocketLockFile(const char *socketfile, bool amPostmaster)
696 {
697         char            lockfile[MAXPGPATH];
698
699         snprintf(lockfile, sizeof(lockfile), "%s.lock", socketfile);
700         if (!CreateLockFile(lockfile, amPostmaster, false, socketfile))
701                 return false;
702         /* Save name of lockfile for TouchSocketLockFile */
703         strcpy(socketLockFile, lockfile);
704         return true;
705 }
706
707 /*
708  * Re-read the socket lock file.  This should be called every so often
709  * to ensure that the lock file has a recent access date.  That saves it
710  * from being removed by overenthusiastic /tmp-directory-cleaner daemons.
711  * (Another reason we should never have put the socket file in /tmp...)
712  */
713 void
714 TouchSocketLockFile(void)
715 {
716         int                     fd;
717         char            buffer[1];
718
719         /* Do nothing if we did not create a socket... */
720         if (socketLockFile[0] != '\0')
721         {
722                 /* XXX any need to complain about errors here? */
723                 fd = open(socketLockFile, O_RDONLY | PG_BINARY, 0);
724                 if (fd >= 0)
725                 {
726                         read(fd, buffer, sizeof(buffer));
727                         close(fd);
728                 }
729         }
730 }
731
732 /*
733  * Append information about a shared memory segment to the data directory
734  * lock file (if we have created one).
735  *
736  * This may be called multiple times in the life of a postmaster, if we
737  * delete and recreate shmem due to backend crash.      Therefore, be prepared
738  * to overwrite existing information.  (As of 7.1, a postmaster only creates
739  * one shm seg anyway; but for the purposes here, if we did have more than
740  * one then any one of them would do anyway.)
741  */
742 void
743 RecordSharedMemoryInLockFile(IpcMemoryKey shmKey, IpcMemoryId shmId)
744 {
745         int                     fd;
746         int                     len;
747         char       *ptr;
748         char            buffer[BLCKSZ];
749
750         /*
751          * Do nothing if we did not create a lockfile (probably because we are
752          * running standalone).
753          */
754         if (directoryLockFile[0] == '\0')
755                 return;
756
757         fd = open(directoryLockFile, O_RDWR | PG_BINARY, 0);
758         if (fd < 0)
759         {
760                 elog(DEBUG, "Failed to rewrite %s: %m", directoryLockFile);
761                 return;
762         }
763         len = read(fd, buffer, sizeof(buffer) - 100);
764         if (len <= 0)
765         {
766                 elog(DEBUG, "Failed to read %s: %m", directoryLockFile);
767                 close(fd);
768                 return;
769         }
770         buffer[len] = '\0';
771
772         /*
773          * Skip over first two lines (PID and path).
774          */
775         ptr = strchr(buffer, '\n');
776         if (ptr == NULL ||
777                 (ptr = strchr(ptr + 1, '\n')) == NULL)
778         {
779                 elog(DEBUG, "Bogus data in %s", directoryLockFile);
780                 close(fd);
781                 return;
782         }
783         ptr++;
784
785         /*
786          * Append shm key and ID.  Format to try to keep it the same length
787          * always (trailing junk won't hurt, but might confuse humans).
788          */
789         sprintf(ptr, "%9lu %9lu\n",
790                         (unsigned long) shmKey, (unsigned long) shmId);
791
792         /*
793          * And rewrite the data.  Since we write in a single kernel call, this
794          * update should appear atomic to onlookers.
795          */
796         len = strlen(buffer);
797         if (lseek(fd, (off_t) 0, SEEK_SET) != 0 ||
798                 (int) write(fd, buffer, len) != len)
799         {
800                 elog(DEBUG, "Failed to write %s: %m", directoryLockFile);
801                 close(fd);
802                 return;
803         }
804         close(fd);
805 }
806
807
808 /*-------------------------------------------------------------------------
809  *                              Version checking support
810  *-------------------------------------------------------------------------
811  */
812
813 /*
814  * Determine whether the PG_VERSION file in directory `path' indicates
815  * a data version compatible with the version of this program.
816  *
817  * If compatible, return. Otherwise, elog(FATAL).
818  */
819 void
820 ValidatePgVersion(const char *path)
821 {
822         char            full_path[MAXPGPATH];
823         FILE       *file;
824         int                     ret;
825         long            file_major,
826                                 file_minor;
827         long            my_major = 0,
828                                 my_minor = 0;
829         char       *endptr;
830         const char *version_string = PG_VERSION;
831
832         my_major = strtol(version_string, &endptr, 10);
833         if (*endptr == '.')
834                 my_minor = strtol(endptr + 1, NULL, 10);
835
836         snprintf(full_path, MAXPGPATH, "%s/PG_VERSION", path);
837
838         file = AllocateFile(full_path, "r");
839         if (!file)
840         {
841                 if (errno == ENOENT)
842                         elog(FATAL, "File %s is missing. This is not a valid data directory.", full_path);
843                 else
844                         elog(FATAL, "cannot open %s: %m", full_path);
845         }
846
847         ret = fscanf(file, "%ld.%ld", &file_major, &file_minor);
848         if (ret == EOF)
849                 elog(FATAL, "cannot read %s: %m", full_path);
850         else if (ret != 2)
851                 elog(FATAL, "`%s' does not have a valid format. You need to initdb.", full_path);
852
853         FreeFile(file);
854
855         if (my_major != file_major || my_minor != file_minor)
856                 elog(FATAL, "The data directory was initialized by PostgreSQL version %ld.%ld, "
857                          "which is not compatible with this version %s.",
858                          file_major, file_minor, version_string);
859 }