From 8b27e8b10ec1c5ac25b3ab93e830a572fcad1316 Mon Sep 17 00:00:00 2001
From: Aditya Kamath <Aditya.Kamath1@ibm.com>
Date: Tue, 3 Feb 2026 08:53:47 -0600
Subject: [PATCH v4] Add AIX support for Postgresql using meson build tool.

This is a patch which makes postgresql project buildable in AIX.

The patch changes are summarised as follows:

1: AIX will support postgresql from oslevel 7.2 onwards for GCC compiler in 64 bit mode only.
   Hence in cppflags and ldflags -maix64 argument has been used in meson.build of the project source directory, since AIX's default compilation
   mode is 32 bit.
2: Changes related to meson build system
   - We have added a new option called -Ddefault_library which will build both shared and static
     library by default and need to be set to shared or static if you want only one of them.
     More about this can be found in the link [0]
   - The disabler()  is used to disable static library in AIX or any platform not needing the same. In every meson.build file we check
     the same via found() and then only add them to the install targets.
3: src/tools/gen_export.pl and src/backend/port/aix/mkldexport.sh assist in creating export files for AIX.

Steps to build postgresql project in AIX using meson:

export OBJECT_MODE=64
meson setup build -Ddefault_library=shared
meson compile -C build

[0]:
https://www.postgresql.org/message-id/e8aa97db-872b-4087-b073-f296baae948d@eisentraut.org

Co-authored-by: Srirama Kucherlapati <sriram.rk@in.ibm.com>
Co-authored-by: Peter Eisentraut <peter@eisentraut.org>
---
 meson.build                                | 61 ++++++++++++++++++++--
 src/backend/meson.build                    | 29 ++++++++++
 src/backend/port/aix/mkldexport.sh         | 59 +++++++++++++++++++++
 src/backend/utils/error/elog.c             |  2 +
 src/common/meson.build                     |  1 +
 src/fe_utils/meson.build                   |  4 +-
 src/include/port/aix.h                     |  0
 src/interfaces/ecpg/compatlib/meson.build  |  8 +--
 src/interfaces/ecpg/ecpglib/meson.build    |  8 +--
 src/interfaces/ecpg/pgtypeslib/meson.build |  9 ++--
 src/interfaces/libpq-oauth/meson.build     |  1 +
 src/interfaces/libpq/meson.build           |  4 +-
 src/makefiles/Makefile.aix                 | 39 ++++++++++++++
 src/port/meson.build                       |  1 +
 src/port/strerror.c                        |  2 +
 src/tools/gen_export.pl                    | 13 ++++-
 16 files changed, 222 insertions(+), 19 deletions(-)
 create mode 100755 src/backend/port/aix/mkldexport.sh
 create mode 100644 src/include/port/aix.h
 create mode 100755 src/makefiles/Makefile.aix

diff --git a/meson.build b/meson.build
index df907b62da3..3c92917bbb5 100644
--- a/meson.build
+++ b/meson.build
@@ -19,6 +19,7 @@ project('postgresql',
   default_options: [
     'warning_level=1', #-Wall equivalent
     'b_pch=false',
+    'default_library=both',
     'buildtype=debugoptimized', # -O2 + debug
     # For compatibility with the autoconf build, set a default prefix. This
     # works even on windows, where it's a drive-relative path (i.e. when on
@@ -51,6 +52,36 @@ thread_dep = dependency('threads')
 auto_features = get_option('auto_features')
 
 
+# Declare dependencies to disable static or shared libraries.  This
+# makes the 'default_library' option work even though we don't use the
+# library() function but instead shared_library() and static_library()
+# separately.
+
+# dep_static_lib n is introduced because in some targets we only want
+# to build shared libraries. Here is why:
+# In AIX, we archive both shared and static libraries.
+# This is because we want multiple shared libraries to co exist in the
+# shared library archive to support backwared compatibility.
+# Meson in AIX archives both shared and static libraries with ninja generator.
+# Link: https://mesonbuild.com/Release-notes-for-1-6-0.html#support-for-openxl-compiler-in-aix
+# So if static and shared libraries have the same name in AIX meson will complain that
+# multiple targets have the same name. In other targets this won't happen since
+# static libraries are archived and shared libraries have suffix ".so" in Linux.
+# To avoid this issue dep_static_lib variable is introduced so targets can decide
+# what they want to build and by default can build both.
+
+# Ex: Windows  we will build both shared and static libraries.
+# Ex: In AIX we want to build only shared libraries.
+
+default_library_opt = get_option('default_library')
+dep_shared_lib = declare_dependency()
+dep_static_lib = declare_dependency()
+if default_library_opt == 'shared'
+  dep_static_lib = disabler()
+elif default_library_opt == 'static'
+  dep_shared_lib = disabler()
+endif
+
 
 ###############################################################
 # Safety first
@@ -325,6 +356,23 @@ elif host_system == 'windows'
 
   windows = import('windows')
 
+elif host_system == 'aix'
+  sema_kind = 'unnamed_posix'
+  library_path_var = 'LIBPATH'
+  export_file_format = 'aix'
+  export_fmt = '-Wl,-bE:@0@'
+  mod_link_args_fmt = ['-Wl,-bI:@0@']
+  mod_link_with_dir = 'libdir'
+  mod_link_with_name = '@0@.imp'
+  dl_suffix = '.a'
+  cppflags += '-maix64'
+  ldflags += '-maix64'
+
+  # M:SRE sets a flag indicating that an object is a shared library.
+  # -brtllib - To link binaries to use runtime loaded shared libraries
+  ldflags_sl += '-Wl,-bM:SRE'
+  ldflags_be += '-Wl,-brtllib'
+
 else
   # XXX: Should we add an option to override the host_system as an escape
   # hatch?
@@ -3528,18 +3576,21 @@ endif
 installed_targets = [
   backend_targets,
   bin_targets,
-  libpq_st,
   pl_targets,
   contrib_targets,
   nls_mo_targets,
   ecpg_targets,
 ]
 
+if dep_static_lib.found()
+  installed_targets += libpq_st
+endif
+
 if oauth_flow_supported
-  installed_targets += [
-    libpq_oauth_so,
-    libpq_oauth_st,
-  ]
+  installed_targets += libpq_oauth_so
+  if dep_static_lib.found()
+    installed_targets += libpq_oauth_st
+  endif
 endif
 
 # all targets that require building code
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 712a857cdb4..511cb6f950c 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -125,6 +125,35 @@ if host_system == 'windows'
     '--FILEDESC', 'PostgreSQL Server',])
 endif
 
+if host_system == 'aix'
+  # AIX does not automatically expose symbols from an executable to shared modules
+  # at runtime. So plugins [ shared modules] cannot see functions inside the
+  # postgres binary unless those symbols are explicitly exported and linked to.
+  # In AIX an EXECUTABLE is not a symbol provider by default.
+  # Shared Modules need to explicitly linked against an export list that declares
+  # which symbols exactly these plugins can use at runtime.
+  # So the mkldexport.sh script contains the "nm" command which extracts symbols
+  # from the object files used to create the postgres binary.
+  # The '.' argument leads mkldexport.sh to emit "#! .", which refers to the
+  # main executable, allowing extension libraries to resolve their undefined
+  # symbols to symbols in the postgres binary.
+  # -Wl, -bE: <Exportfile> "Tells these symbols are exported by postgres binary"
+  # -Wl,-bI: <Exportfile> "Tells AIX linker/loader that this plugin import these
+  # symbols from the postgres binary as is in the export file.
+  postgres_imp = custom_target('postgres.imp',
+    command: [files('port/aix/mkldexport.sh'), '@INPUT@', '.'],
+    input: postgres_lib,
+    output: 'postgres.imp',
+    capture: true,
+    install: true,
+    install_dir: dir_lib,
+    build_by_default: false,
+  )
+  backend_link_args += '-Wl,-bE:@0@'.format(postgres_imp.full_path())
+  backend_link_depends += postgres_imp
+endif
+
+
 postgres = executable('postgres',
   backend_input,
   sources: post_export_backend_sources,
diff --git a/src/backend/port/aix/mkldexport.sh b/src/backend/port/aix/mkldexport.sh
new file mode 100755
index 00000000000..ae3106ba087
--- /dev/null
+++ b/src/backend/port/aix/mkldexport.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# mkldexport
+#	create an AIX exports file from an object file
+#
+# src/backend/port/aix/mkldexport.sh
+#
+# Usage:
+#	mkldexport objectfile [location]
+# where
+#	objectfile is the current location of the object file.
+#	location is the eventual (installed) location of the
+#		object file (if different from the current
+#		working directory).
+#
+# Header: /usr/local/devel/postgres/src/tools/mkldexport/RCS/mkldexport.sh,v 1.2 1994/03/13 04:59:12 aoki Exp
+#
+# In AIX, nm in multi-mode command where its behaviour depends on the OBJECT_MODE
+# When OBJECT_MODE=64 nm will only allow 64 bit objects to process.
+export OBJECT_MODE=64
+# Search for the nm command binary.
+if [ -x /usr/ucb/nm ]
+then NM=/usr/ucb/nm
+elif [ -x /usr/bin/nm ]
+then NM=/usr/bin/nm
+elif [ -x /usr/ccs/bin/nm ]
+then NM=/usr/ccs/bin/nm
+elif [ -x /usr/usg/bin/nm ]
+then NM=/usr/usg/bin/nm
+else echo "Fatal error: cannot find `nm' ... please check your installation."
+     exit 1
+fi
+
+CMDNAME=`basename $0`
+if [ -z "$1" ]; then
+	echo "Usage: $CMDNAME object [location]"
+	exit 1
+fi
+OBJNAME=`basename $1`
+if [ "`basename $OBJNAME`" != "`basename $OBJNAME .o`" ]; then
+	OBJNAME=`basename $OBJNAME .o`.so
+fi
+if [ -z "$2" ]; then
+	echo '#!'
+else
+	if [ "$2" = "." ]; then
+		# for the base executable (AIX 4.2 and up)
+		echo '#! .'
+	else
+		echo '#!' $2
+	fi
+fi
+$NM -BCg $1 | \
+	egrep ' [TDB] ' | \
+	sed -e 's/.* //' | \
+	egrep -v '\$' | \
+	sed -e 's/^[.]//' | \
+	sort | \
+	uniq
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index aa530d3685e..c44fe84e0f5 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -914,9 +914,11 @@ errcode_for_file_access(void)
 			/* Wrong object type or state */
 		case ENOTDIR:			/* Not a directory */
 		case EISDIR:			/* Is a directory */
+#if defined(ENOTEMPTY) && (ENOTEMPTY != EEXIST) /* same code on AIX */
 		case ENOTEMPTY:			/* Directory not empty */
 			edata->sqlerrcode = ERRCODE_WRONG_OBJECT_TYPE;
 			break;
+#endif
 
 			/* Insufficient resources */
 		case ENOSPC:			/* No space left on device */
diff --git a/src/common/meson.build b/src/common/meson.build
index b757618a9c9..1417ac3bbb7 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -192,6 +192,7 @@ foreach name, opts : pgcommon_variants
           opts.get('include_directories', []),
         ],
         'dependencies': opts['dependencies'] + [ssl],
+        'install': dep_static_lib.found(),
       }
     )
   pgcommon += {name: lib}
diff --git a/src/fe_utils/meson.build b/src/fe_utils/meson.build
index a2420ea2d5c..9c9e0bcc0da 100644
--- a/src/fe_utils/meson.build
+++ b/src/fe_utils/meson.build
@@ -35,5 +35,7 @@ fe_utils = static_library('libpgfeutils',
   include_directories: [postgres_inc, libpq_inc],
   c_args: host_system == 'windows' ? ['-DFD_SETSIZE=1024'] : [],
   dependencies: frontend_common_code,
-  kwargs: default_lib_args,
+  kwargs: default_lib_args + {
+            'install': dep_static_lib.found(),
+          },
 )
diff --git a/src/include/port/aix.h b/src/include/port/aix.h
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/interfaces/ecpg/compatlib/meson.build b/src/interfaces/ecpg/compatlib/meson.build
index 6cb1be73407..94dcc67c03c 100644
--- a/src/interfaces/ecpg/compatlib/meson.build
+++ b/src/interfaces/ecpg/compatlib/meson.build
@@ -20,17 +20,19 @@ ecpg_compat_st = static_library('libecpg_compat',
   ecpg_compat_sources,
   include_directories: ecpg_compat_inc,
   c_args: ecpg_compat_c_args,
-  dependencies: [frontend_stlib_code, thread_dep],
+  dependencies: [dep_static_lib, frontend_stlib_code, thread_dep],
   link_with: [ecpglib_st, ecpg_pgtypes_st],
   kwargs: default_lib_args,
 )
-ecpg_targets += ecpg_compat_st
+if dep_static_lib.found()
+  ecpg_targets += ecpg_compat_st
+endif
 
 ecpg_compat_so = shared_library('libecpg_compat',
   ecpg_compat_sources + ecpg_compat_so_sources,
   include_directories: ecpg_compat_inc,
   c_args: ecpg_compat_c_args,
-  dependencies: [frontend_shlib_code, thread_dep],
+  dependencies: [dep_shared_lib, frontend_shlib_code, thread_dep],
   link_with: [ecpglib_so, ecpg_pgtypes_so],
   soversion: host_system != 'windows' ? '3' : '',
   darwin_versions: ['3', '3.' + pg_version_major.to_string()],
diff --git a/src/interfaces/ecpg/ecpglib/meson.build b/src/interfaces/ecpg/ecpglib/meson.build
index 889bd9efd65..a81d772aa69 100644
--- a/src/interfaces/ecpg/ecpglib/meson.build
+++ b/src/interfaces/ecpg/ecpglib/meson.build
@@ -30,18 +30,20 @@ ecpglib_st = static_library('libecpg',
   include_directories: ecpglib_inc,
   c_args: ecpglib_c_args,
   c_pch: pch_postgres_fe_h,
-  dependencies: [frontend_stlib_code, thread_dep, libpq],
+  dependencies: [dep_static_lib, frontend_stlib_code, thread_dep, libpq],
   link_with: [ecpg_pgtypes_st],
   kwargs: default_lib_args,
 )
-ecpg_targets += ecpglib_st
+if dep_static_lib.found()
+  ecpg_targets += ecpglib_st
+endif
 
 ecpglib_so = shared_library('libecpg',
   ecpglib_sources + ecpglib_so_sources,
   include_directories: ecpglib_inc,
   c_args: ecpglib_c_args,
   c_pch: pch_postgres_fe_h,
-  dependencies: [frontend_shlib_code, libpq, thread_dep],
+  dependencies: [dep_shared_lib, frontend_shlib_code, libpq, thread_dep],
   link_with: ecpg_pgtypes_so,
   soversion: host_system != 'windows' ? '6' : '',
   darwin_versions: ['6', '6.' + pg_version_major.to_string()],
diff --git a/src/interfaces/ecpg/pgtypeslib/meson.build b/src/interfaces/ecpg/pgtypeslib/meson.build
index 6b78f529e53..019cf0e36f6 100644
--- a/src/interfaces/ecpg/pgtypeslib/meson.build
+++ b/src/interfaces/ecpg/pgtypeslib/meson.build
@@ -26,17 +26,20 @@ ecpg_pgtypes_st = static_library('libpgtypes',
   include_directories: ecpg_pgtypes_inc,
   c_args: ecpg_pgtypes_c_args,
   c_pch: pch_postgres_fe_h,
-  dependencies: frontend_stlib_code,
+  dependencies: [dep_static_lib, frontend_stlib_code],
   kwargs: default_lib_args,
 )
-ecpg_targets += ecpg_pgtypes_st
+
+if dep_static_lib.found()
+  ecpg_targets += ecpg_pgtypes_st
+endif
 
 ecpg_pgtypes_so = shared_library('libpgtypes',
   ecpg_pgtypes_sources + ecpg_pgtypes_so_sources,
   include_directories: ecpg_pgtypes_inc,
   c_args: ecpg_pgtypes_c_args,
   c_pch: pch_postgres_fe_h,
-  dependencies: frontend_shlib_code,
+  dependencies: [dep_shared_lib, frontend_shlib_code],
   version: '3.' + pg_version_major.to_string(),
   soversion: host_system != 'windows' ? '3' : '',
   darwin_versions: ['3', '3.' + pg_version_major.to_string()],
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
index d8a0c04095a..b0083f279aa 100644
--- a/src/interfaces/libpq-oauth/meson.build
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -26,6 +26,7 @@ libpq_oauth_st = static_library('libpq-oauth',
   include_directories: [libpq_oauth_inc, postgres_inc],
   c_pch: pch_postgres_fe_h,
   dependencies: [
+    dep_static_lib,
     frontend_stlib_code,
     libpq_oauth_deps,
     ssl, # libpq-int.h includes OpenSSL headers
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index c5ecd9c3a87..cb75f96c6a2 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -62,7 +62,7 @@ libpq_st = static_library('libpq',
   include_directories: [libpq_inc],
   c_args: libpq_c_args,
   c_pch: pch_postgres_fe_h,
-  dependencies: [frontend_stlib_code, libpq_deps],
+  dependencies: [dep_static_lib, frontend_stlib_code, libpq_deps],
   kwargs: default_lib_args,
 )
 
@@ -74,7 +74,7 @@ libpq_so = shared_library('libpq',
   version: '5.' + pg_version_major.to_string(),
   soversion: host_system != 'windows' ? '5' : '',
   darwin_versions: ['5', '5.' + pg_version_major.to_string()],
-  dependencies: [frontend_shlib_code, libpq_deps],
+  dependencies: [dep_shared_lib, frontend_shlib_code, libpq_deps],
   link_depends: export_file,
   link_args: export_fmt.format(export_file.full_path()),
   kwargs: default_lib_args,
diff --git a/src/makefiles/Makefile.aix b/src/makefiles/Makefile.aix
new file mode 100755
index 00000000000..42bdbade5bb
--- /dev/null
+++ b/src/makefiles/Makefile.aix
@@ -0,0 +1,39 @@
+# -blibpath:
+# The path to be inserted into the default path (Index 0 path) field of the
+# loader section. When this flag is presented, the -L paths will not be stored.
+# AIX uses a stricter, more explicit approach. The runtime linker expects to
+# tell it exactly where to look using -blibpath.
+# -blibpath must contain ALL directories where we should look for libraries
+libpath := $(shell echo $(subst -L,:,$(filter -L/%,$(LDFLAGS))) | sed -e's/ //g'):/usr/lib:/lib
+
+# when building with gcc, need to make sure that libgcc can be found
+ifeq ($(GCC), yes)
+libpath += :$(libpath):$(dir $(shell gcc -print-libgcc-file-name))
+endif
+
+# AIX uses a stricter, more explicit approach. The runtime linker expects
+# to tell it exactly where to look using -blibpath.
+rpath = -Wl,-blibpath:'$(rpathdir)$(libpath)'
+
+# gcc needs to know it's building a shared lib, otherwise it'll not emit
+# correct code / link to the right support libraries
+ifeq ($(GCC), yes)
+LDFLAGS_SL += -shared
+endif
+
+# env var name to use in place of LD_LIBRARY_PATH
+ld_library_path_var = LIBPATH
+
+POSTGRES_IMP= postgres.imp
+
+ifdef PGXS
+BE_DLLLIBS= -Wl,-bI:$(pkglibdir)/$(POSTGRES_IMP)
+else
+BE_DLLLIBS= -Wl,-bI:$(top_builddir)/src/backend/$(POSTGRES_IMP)
+endif
+
+MKLDEXPORT_DIR=src/backend/port/aix
+MKLDEXPORT=$(top_srcdir)/$(MKLDEXPORT_DIR)/mkldexport.sh
+
+%$(DLSUFFIX): %.o
+    $(CC) $(CFLAGS) $*.o $(LDFLAGS) $(LDFLAGS_SL) -o $@ $(BE_DLLLIBS)
diff --git a/src/port/meson.build b/src/port/meson.build
index d7d4e705b89..a8d92937581 100644
--- a/src/port/meson.build
+++ b/src/port/meson.build
@@ -192,6 +192,7 @@ foreach name, opts : pgport_variants
       c_pch: pch_c_h,
       kwargs: opts + {
         'dependencies': opts['dependencies'] + [ssl],
+        'install': dep_static_lib.found(),
       }
     )
   pgport += {name: lib}
diff --git a/src/port/strerror.c b/src/port/strerror.c
index 76af67ec2d0..d70734196b2 100644
--- a/src/port/strerror.c
+++ b/src/port/strerror.c
@@ -214,8 +214,10 @@ get_errno_symbol(int errnum)
 			return "ENOTCONN";
 		case ENOTDIR:
 			return "ENOTDIR";
+#if defined(ENOTEMPTY) && (ENOTEMPTY != EEXIST) /* same code on AIX */
 		case ENOTEMPTY:
 			return "ENOTEMPTY";
+#endif
 		case ENOTSOCK:
 			return "ENOTSOCK";
 #ifdef ENOTSUP
diff --git a/src/tools/gen_export.pl b/src/tools/gen_export.pl
index 515f6d4a7a6..04e4cb5cbba 100644
--- a/src/tools/gen_export.pl
+++ b/src/tools/gen_export.pl
@@ -18,9 +18,10 @@ GetOptions(
 
 if (not(   $format eq 'darwin'
 		or $format eq 'gnu'
-		or $format eq 'win'))
+		or $format eq 'win'
+        or $format eq 'aix'))
 {
-	die "$0: $format is not yet handled (only darwin, gnu, win are)\n";
+	die "$0: $format is not yet handled (only darwin, gnu, win, aix are)\n";
 }
 
 open(my $input_handle, '<', $input)
@@ -46,6 +47,10 @@ elsif ($format eq 'win')
 	}
 	print $output_handle "EXPORTS\n";
 }
+elsif ($format eq 'aix')
+{
+	print $output_handle "#!\n";
+}
 
 while (<$input_handle>)
 {
@@ -63,6 +68,10 @@ while (<$input_handle>)
 		{
 			print $output_handle "    $1;\n";
 		}
+        elsif ($format eq 'aix')
+        {
+            print $output_handle "$1\n";
+        }
 		elsif ($format eq 'win')
 		{
 			print $output_handle "$1 @ $2\n";
-- 
2.51.2

