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