Introduce frontend API able to retrieve the contents of PG_VERSION
authorMichael Paquier <michael@paquier.xyz>
Tue, 14 Oct 2025 07:20:42 +0000 (16:20 +0900)
committerMichael Paquier <michael@paquier.xyz>
Tue, 14 Oct 2025 07:20:42 +0000 (16:20 +0900)
get_pg_version() is able to return a version number, that can be used
for comparisons based on PG_VERSION_NUM.  A macro is added to convert
the result to a major version number, to work with PG_MAJORVERSION_NUM.

It is possible to pass to the routine an optional argument, where the
contents retrieved from PG_VERSION are saved.  This requirement matters
for some of the frontend code (one example: pg_upgrade wants that for
tablespace paths with a version number strictly older than v10).

This will be used by a set of follow-up patches, to be consumed in
various frontend tools that duplicate a logic similar to do what this
new routine does, like:
- pg_resetwal
- pg_combinebackup
- pg_createsubscriber
- pg_upgrade

This routine supports both the post-v10 version number and the older
flavor (aka 9.6), as required at least by pg_upgrade.

Author: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Discussion: https://postgr.es/m/aOiirvWJzwdVCXph@paquier.xyz

src/fe_utils/Makefile
src/fe_utils/meson.build
src/fe_utils/version.c [new file with mode: 0644]
src/include/fe_utils/version.h [new file with mode: 0644]

index 28196ce0f6ae69c9a0860cbf8d42a457553e8709..b9fd566ff873b20d967e6ebfca177af3f18f93fb 100644 (file)
@@ -37,7 +37,8 @@ OBJS = \
    query_utils.o \
    recovery_gen.o \
    simple_list.o \
-   string_utils.o
+   string_utils.o \
+   version.o
 
 ifeq ($(PORTNAME), win32)
 override CPPFLAGS += -DFD_SETSIZE=1024
index 5a9ddb73463e19bb440d0f524fe4725023a9aa3f..ddac3c3a65843c25cfeaa0934b8fc784d6c6729d 100644 (file)
@@ -18,6 +18,7 @@ fe_utils_sources = files(
   'recovery_gen.c',
   'simple_list.c',
   'string_utils.c',
+  'version.c',
 )
 
 psqlscan = custom_target('psqlscan',
diff --git a/src/fe_utils/version.c b/src/fe_utils/version.c
new file mode 100644 (file)
index 0000000..94f0923
--- /dev/null
@@ -0,0 +1,86 @@
+/*-------------------------------------------------------------------------
+ *
+ * version.c
+ *     Routine to retrieve information of PG_VERSION
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *   src/fe_utils/version.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <sys/stat.h>
+
+#include "common/logging.h"
+#include "fe_utils/version.h"
+
+/*
+ * Assumed maximum size of PG_VERSION.  This should be more than enough for
+ * any version numbers that need to be handled.
+ */
+#define PG_VERSION_MAX_SIZE    64
+
+/*
+ * get_pg_version
+ *
+ * Retrieve the major version number of the given data folder, from
+ * PG_VERSION.  The result returned is a version number, that can be used
+ * for comparisons based on PG_VERSION_NUM.  For example, if PG_VERSION
+ * contains "18\n", this function returns 180000.
+ *
+ * This supports both the pre-v10 and the post-v10 version numbering.
+ *
+ * Optionally, "version_str" can be specified to store the contents
+ * retrieved from PG_VERSION.  It is allocated by this routine; the
+ * caller is responsible for pg_free()-ing it.
+ */
+uint32
+get_pg_version(const char *datadir, char **version_str)
+{
+   FILE       *version_fd;
+   char        ver_filename[MAXPGPATH];
+   char        buf[PG_VERSION_MAX_SIZE];
+   int         v1 = 0,
+               v2 = 0;
+   struct stat st;
+
+   snprintf(ver_filename, sizeof(ver_filename), "%s/PG_VERSION",
+            datadir);
+
+   if ((version_fd = fopen(ver_filename, "r")) == NULL)
+       pg_fatal("could not open version file \"%s\": %m", ver_filename);
+
+   if (fstat(fileno(version_fd), &st) != 0)
+       pg_fatal("could not stat file \"%s\": %m", ver_filename);
+   if (st.st_size > PG_VERSION_MAX_SIZE)
+       pg_fatal("file \"%s\" is too large", ver_filename);
+
+   if (fscanf(version_fd, "%63s", buf) == 0 ||
+       sscanf(buf, "%d.%d", &v1, &v2) < 1)
+       pg_fatal("could not parse version file \"%s\"", ver_filename);
+
+   fclose(version_fd);
+
+   if (version_str)
+   {
+       *version_str = pg_malloc(PG_VERSION_MAX_SIZE);
+       memcpy(*version_str, buf, st.st_size);
+   }
+
+   if (v1 < 10)
+   {
+       /* pre-v10 style, e.g. 9.6.1 */
+       return v1 * 10000 + v2 * 100;
+   }
+   else
+   {
+       /* post-v10 style, e.g. 10.1 */
+       return v1 * 10000;
+   }
+}
diff --git a/src/include/fe_utils/version.h b/src/include/fe_utils/version.h
new file mode 100644 (file)
index 0000000..cea9d80
--- /dev/null
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * Routines to retrieve information of PG_VERSION
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/fe_utils/version.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_VERSION_H
+#define PG_VERSION_H
+
+/*
+ * Retrieve the version major number, useful for major version checks
+ * based on PG_MAJORVERSION_NUM.
+ */
+#define GET_PG_MAJORVERSION_NUM(v) ((v) / 10000)
+
+extern uint32 get_pg_version(const char *datadir, char **version_str);
+
+#endif                         /* PG_VERSION_H */