public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Michal Schorm <mschorm@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/unixODBC] rawhide: [Fedora 45 Change] Add drop-in directory infrastructure for ODBC driver registration
Date: Wed, 29 Jul 2026 11:11:51 GMT	[thread overview]
Message-ID: <178532351199.1.9385421156362987254.rpms-unixODBC-2d0d3091432e@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/unixODBC
            Branch : rawhide
            Commit : 2d0d3091432e5c382d5cc081517733d47e2e3e32
            Author : Michal Schorm <mschorm@redhat.com>
            Date   : 2026-07-29T12:39:26+02:00
            Stats  : +597/-4 in 3 file(s)
            URL    : https://src.fedoraproject.org/rpms/unixODBC/c/2d0d3091432e5c382d5cc081517733d47e2e3e32?branch=rawhide

            Log:
            [Fedora 45 Change] Add drop-in directory infrastructure for ODBC driver registration

Replace the static '%config(noreplace) /etc/odbcinst.ini' with a
generated '%ghost' file assembled from per-driver drop-in snippets,
using RPM file triggers.

Drop-in infrastructure:
- Each driver package ships a static '.ini' snippet to
  '/usr/lib/odbc/odbcinst.d/' (vendor defaults)
- Administrators override or add drivers via '/etc/odbc/odbcinst.d/'
- '%transfiletriggerin' / '%transfiletriggerpostun' invoke
  'odbcinst-generate' whenever snippets are installed or removed
- 'odbcinst-generate' merges all snippets, resolves overrides, and
  atomically replaces '/etc/odbcinst.ini'

Shared subpackage ('odbcinst-generate'):
- Named driver-manager-neutral (not 'unixODBC-odbcinst-generate')
  because both unixODBC and iODBC are equal consumers — follows the
  pattern of neutral infrastructure packages like 'hicolor-icon-theme'
  and 'crypto-policies'
- 'BuildArch: noarch' — contains only a bash script, man page, license,
  '%ghost' config, '%dir' entries, and RPM file triggers; nothing compiled
- Contains 'odbcinst-generate', its man page, the drop-in directories,
  the '%ghost' config, and the RPM file triggers

Override semantics (stripped-name matching):
- The numeric prefix is stripped from each filename ('10-mariadb.ini'
  becomes 'mariadb.ini'); files sharing a stripped name compete
- Admin directory always wins over vendor directory for the same
  stripped name, regardless of numeric prefix
- Within the same directory, higher numeric prefix wins
- Output sections sorted alphabetically by stripped name
- Symlink to '/dev/null' disables a driver

This differs from the exact-filename matching used by 'sysctl.d',
'tmpfiles.d', and 'modprobe.d', where '/etc/' and '/usr/lib/' files
with different prefixes coexist and conflicting keys are resolved
individually. ODBC registration uses whole-section replacement
because each snippet declares a complete '[DriverName]' INI section
and merging keys from two '[MariaDB]' sections would be ambiguous.

Numeric prefix ranges:
- 10-29: distro vendor (Fedora-shipped driver packages)
- 30-49: third-party / out-of-tree (COPR, vendor RPMs)
- 50-99: administrator overrides and additions

Path resolution with multilib 'Driver64' support:
- Snippets use bare library names ('Driver = libmaodbc.so') for
  noarch portability
- 'odbcinst-generate' probes each bare name per-driver:
  '/usr/lib64/odbc/' and '/usr/lib/odbc/'
- When both 64-bit and 32-bit '.so' exist (multilib), emit
  'Driver = /usr/lib/odbc/...' and 'Driver64 = /usr/lib64/odbc/...'
- unixODBC selects the matching key based on the calling application's
  word size (compile-time '#ifdef PLATFORM64'), so both 64-bit and
  32-bit clients (e.g. Wine) load the correct driver
- When only one architecture is present, only 'Driver =' is emitted
- Same logic applies to 'Setup'/'Setup64'
- If not found in either directory, the bare name is left unchanged
  so unixODBC can still locate it via its compiled-in search path

iODBC multilib limitation:
- iODBC does not support the 'Driver64' key — its driver loading
  code ('_iodbcdm_driverload()' in 'iodbc/connect.c') reads only
  'Driver', and its dlopen wrapper ('DLL_OPEN' macro in
  'iodbcinst/dlf.h') calls 'dlopen()' directly with no multilib
  awareness or search-path fallback
- iODBC ignores unknown keys harmlessly, so the extra 'Driver64'
  line causes no errors — it is simply never read
- On iODBC-only hosts, 'Driver =' provides the correct path
  regardless (32-bit path when multilib, 64-bit when only one arch)

Migration ('%config(noreplace)' to '%ghost' transition):
- '%pretrans' Lua scriptlet parses '/etc/odbcinst.ini' for INI section
  headers and checks against known Fedora drivers ('PostgreSQL',
  'MySQL', 'FreeTDS', 'MariaDB', 'MDBTools', 'SQLITE', 'SQLITE3')
- Stock configs (only known sections) are silently dropped — the
  drop-in snippets regenerate equivalent entries
- Configs with unknown sections (user-added custom drivers) are saved
  as '.rpmsave' with a '%post' migration notice
- A checksum approach was not viable: 22 stock file variants across
  Fedora versions, plus 'sqliteodbc' '%post' injected arch-specific
  sections via 'odbcinst -i'; RPM's Lua has no digest functions

File additions:
- 'odbcinst-generate': regeneration script installed to '%{_bindir}'
- 'odbcinst-generate.1': man page documenting drop-in layout, override
  semantics, path resolution, multilib handling, file conflicts,
  numeric ranges, and design rationale vs other Fedora drop-in systems

This commit is part of a Fedora 45 Self Contained Change:
  https://fedoraproject.org/wiki/Changes/ODBC_stack_modernization

Substantial feedback provided by: Peter Lemenkov <plemenko@redhat.com>

Co-Authored-By: Claude AI <noreply@anthropic.com>

---
diff --git a/odbcinst-generate b/odbcinst-generate
new file mode 100644
index 0000000..99f10d7
--- /dev/null
+++ b/odbcinst-generate
@@ -0,0 +1,123 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2026 Michal Schorm <mschorm@redhat.com>
+#
+# Regenerate /etc/odbcinst.ini from drop-in snippets.
+#
+# Merge order:
+#   1. /usr/lib/odbc/odbcinst.d/*.ini  (vendor, low-numbered prefixes)
+#   2. /etc/odbc/odbcinst.d/*.ini      (admin, high-numbered prefixes)
+#
+# Override rules:
+#   - Files are sorted by full basename (numeric prefix determines order)
+#   - When two files share the same name after stripping the numeric prefix
+#     (e.g., 10-mariadb.ini and 60-mariadb.ini), the higher-numbered one wins
+#   - A symlink to /dev/null disables the corresponding driver entry
+#
+# This script is called from RPM file triggers and %post.
+# It must be safe to run at any time (idempotent).
+
+set -eo pipefail
+
+VENDOR_DIR=/usr/lib/odbc/odbcinst.d
+ADMIN_DIR=/etc/odbc/odbcinst.d
+TARGET=/etc/odbcinst.ini
+
+tmpfile=$(mktemp "${TARGET}.XXXXXX")
+resolved=""
+trap 'rm -f "$tmpfile" "$resolved"' EXIT
+
+cat > "$tmpfile" << 'HEADER'
+# Auto-generated from drop-in snippets.  Do not edit this file.
+#
+# Vendor defaults:  /usr/lib/odbc/odbcinst.d/*.ini
+# Admin overrides:  /etc/odbc/odbcinst.d/*.ini
+#
+# To override a vendor driver, place a file with the same name
+# (or higher numeric prefix) in /etc/odbc/odbcinst.d/.
+# To disable a vendor driver, symlink its snippet to /dev/null:
+#   ln -sf /dev/null /etc/odbc/odbcinst.d/10-freetds.ini
+#
+# To apply changes after editing drop-in files, run:
+#   odbcinst-generate
+HEADER
+
+# Build an associative array: key → path (last writer wins by sort order)
+declare -A snippets
+
+# Process vendor first (lower priority), then admin (higher priority)
+for dir in "$VENDOR_DIR" "$ADMIN_DIR"; do
+    [ -d "$dir" ] || continue
+    for f in "$dir"/*.ini; do
+        [ -e "$f" ] || continue
+        base=$(basename "$f")
+        # Strip numeric prefix for override matching: "10-mariadb.ini" → "mariadb.ini"
+        key="${base#[0-9][0-9]-}"
+        snippets["$key"]="$f"
+    done
+done
+
+# Sort keys and emit content
+if [ ${#snippets[@]} -gt 0 ]; then
+    while IFS= read -r key; do
+        f="${snippets[$key]}"
+
+        # Symlink to /dev/null = disabled
+        if [ -L "$f" ] && [ "$(readlink -f "$f")" = "/dev/null" ]; then
+            continue
+        fi
+
+        [ -f "$f" ] || continue
+        echo "" >> "$tmpfile"
+        cat "$f" >> "$tmpfile"
+    done < <(printf '%s\n' "${!snippets[@]}" | sort)
+fi
+
+# Resolve bare library names (e.g. "libmaodbc.so") to absolute paths.
+# unixODBC finds bare names via its compiled-in --with-odbc-driver-path,
+# but iODBC requires absolute paths for dlopen().  Absolute paths also
+# make the generated config self-documenting.
+#
+# Multilib: when a driver .so exists in both /usr/lib64/odbc/ and
+# /usr/lib/odbc/ (e.g. both x86_64 and i686 RPMs installed), emit
+# Driver= for the 32-bit path and Driver64= for the 64-bit path.
+# unixODBC selects the matching key based on the calling application's
+# word size.  When only one architecture is present, only Driver= is
+# emitted.  Same for Setup/Setup64.
+#
+# If a bare name is not found in either directory, it is left unchanged
+# so unixODBC can still find it via its built-in search path.
+resolved=$(mktemp "${TARGET}.XXXXXX")
+
+while IFS= read -r line; do
+    if [[ "$line" =~ ^((Driver|Setup)[[:space:]]*=[[:space:]]*)([^/[:space:]][^/[:space:]]*\.so[^/[:space:]]*)[[:space:]]*$ ]]; then
+        prefix="${BASH_REMATCH[1]}"
+        key="${BASH_REMATCH[2]}"
+        bare="${BASH_REMATCH[3]}"
+
+        in_lib64=""
+        in_lib32=""
+        [ -f "/usr/lib64/odbc/$bare" ] && in_lib64="/usr/lib64/odbc/$bare"
+        [ -f "/usr/lib/odbc/$bare" ]   && in_lib32="/usr/lib/odbc/$bare"
+
+        if [ -n "$in_lib64" ] && [ -n "$in_lib32" ]; then
+            printf '%s%s\n' "$prefix" "$in_lib32"
+            printf '%s64 = %s\n' "$key" "$in_lib64"
+        elif [ -n "$in_lib64" ]; then
+            printf '%s%s\n' "$prefix" "$in_lib64"
+        elif [ -n "$in_lib32" ]; then
+            printf '%s%s\n' "$prefix" "$in_lib32"
+        else
+            printf '%s\n' "$line"
+        fi
+    else
+        printf '%s\n' "$line"
+    fi
+done < "$tmpfile" > "$resolved"
+
+mv "$resolved" "$tmpfile"
+
+# Atomic replace
+chmod 644 "$tmpfile"
+mv "$tmpfile" "$TARGET"
+trap - EXIT

diff --git a/odbcinst-generate.1 b/odbcinst-generate.1
new file mode 100644
index 0000000..f989383
--- /dev/null
+++ b/odbcinst-generate.1
@@ -0,0 +1,357 @@
+.\" SPDX-License-Identifier: GPL-2.0-or-later
+.\" Copyright (C) 2026 Michal Schorm <mschorm@redhat.com>
+.TH ODBCINST\-GENERATE 1 "July 2026" "unixODBC" "User Commands"
+.SH NAME
+odbcinst\-generate \- regenerate /etc/odbcinst.ini from drop\-in snippets
+.SH SYNOPSIS
+.B odbcinst\-generate
+.SH DESCRIPTION
+.B odbcinst\-generate
+assembles
+.I /etc/odbcinst.ini
+from drop\-in snippet files shipped by ODBC driver packages and
+administrator overrides.
+It is normally invoked automatically by RPM file triggers when driver
+packages are installed or removed.
+Administrators may also run it manually after editing drop\-in files.
+.PP
+The generated file is replaced atomically (write to a temporary file,
+then rename).
+The command is idempotent and safe to run at any time.
+.SH DROP\-IN DIRECTORIES
+.TP
+.I /usr/lib/odbc/odbcinst.d/
+Vendor defaults.
+ODBC driver packages ship static
+.B .ini
+snippet files here.
+These files are never modified by the administrator.
+.TP
+.I /etc/odbc/odbcinst.d/
+Administrator overrides and additions.
+Files placed here take precedence over vendor defaults.
+.SH FILE NAMING
+Snippet files are named
+.IR NN \- name .ini ,
+where
+.I NN
+is a two\-digit numeric prefix that controls merge order.
+.PP
+Recommended ranges:
+.RS
+.TP
+.B 10\(en29
+Distro vendor (Fedora\-shipped driver packages).
+.TP
+.B 30\(en49
+Third\-party / out\-of\-tree (COPR packages, vendor RPMs such as
+SeerODBC, Oracle Instant Client, etc.).
+.TP
+.B 50\(en99
+Administrator overrides and additions.
+.RE
+.PP
+The numeric prefix controls tie\-breaking within a single directory
+(see
+.BR "OVERRIDE SEMANTICS" ).
+It does not affect output order; output sections are sorted
+alphabetically by the stripped name.
+.SH OVERRIDE SEMANTICS
+Files from both directories are merged using
+.B stripped\-name matching:
+the numeric prefix is removed from each filename
+.RI ( 10\-mariadb.ini
+becomes
+.IR mariadb.ini ),
+and files that share the same stripped name compete.
+Only one file per stripped name survives; the rest are discarded.
+.PP
+Tie\-breaking follows two rules, applied in order:
+.IP 1. 4
+.B Directory wins.
+A file in the administrator directory
+.RI ( /etc/odbc/odbcinst.d/ )
+always takes precedence over a file with the same stripped name in the
+vendor directory
+.RI ( /usr/lib/odbc/odbcinst.d/ ),
+regardless of numeric prefix.
+.IP 2. 4
+.B Higher prefix wins.
+Within the same directory, the file with the higher numeric prefix wins.
+For example,
+.I 30\-mariadb.ini
+overrides
+.I 10\-mariadb.ini
+when both reside in the vendor directory.
+.PP
+After tie\-breaking, the surviving snippets are sorted alphabetically by
+their stripped name and concatenated to produce the output file.
+.SH DISABLING A DRIVER
+To disable a vendor\-shipped driver, create a symlink to
+.I /dev/null
+in the administrator directory:
+.PP
+.RS
+.nf
+ln \-sf /dev/null /etc/odbc/odbcinst.d/10\-freetds.ini
+odbcinst\-generate
+.fi
+.RE
+.PP
+Symlinks pointing to
+.I /dev/null
+are detected and skipped during regeneration.
+.SH PATH RESOLUTION
+Snippet files should use bare library names for the
+.B Driver
+(and
+.BR Setup )
+keys:
+.PP
+.RS
+.nf
+Driver = libmaodbc.so
+.fi
+.RE
+.PP
+During generation,
+.B odbcinst\-generate
+resolves each bare name to an absolute, architecture\-correct path
+by probing the filesystem per\-driver.
+For each bare
+.B .so
+name, it checks both
+.I /usr/lib64/odbc/
+and
+.IR /usr/lib/odbc/ .
+.PP
+.SS Multilib resolution
+On a multilib system where both the 64\-bit and 32\-bit packages of
+a driver are installed (e.g.\&
+.I mariadb\-connector\-odbc.x86_64
+and
+.IR mariadb\-connector\-odbc.i686 ),
+the generator emits two keys:
+.PP
+.RS
+.nf
+Driver   = /usr/lib/odbc/libmaodbc.so
+Driver64 = /usr/lib64/odbc/libmaodbc.so
+.fi
+.RE
+.PP
+unixODBC selects the matching key based on the calling application\(aqs
+word size: a 64\-bit application reads
+.BR Driver64 ,
+a 32\-bit application (e.g.\& Wine) reads
+.BR Driver .
+The same logic applies to
+.BR Setup / Setup64 .
+.PP
+When only one architecture is present, only
+.B Driver
+is emitted with that architecture\(aqs path.
+If the bare name is not found in either directory, it is left unchanged
+so that unixODBC can still locate it via its compiled\-in driver search
+path.
+.PP
+.SS Edge cases
+.IP \(bu 3
+A 32\-bit\-only driver on a 64\-bit host is correctly resolved to
+.I /usr/lib/odbc/
+instead of
+.IR /usr/lib64/odbc/ .
+.IP \(bu 3
+A third\-party driver whose shared library lives outside
+.I /usr/lib*/odbc/
+(e.g.\& in
+.IR /usr/lib64/ )
+is not rewritten to a non\-existent path.
+.IP \(bu 3
+iODBC does not recognize the
+.B Driver64
+key, but ignores unknown keys harmlessly.
+On iODBC\-only hosts, the
+.B Driver
+key provides the correct path.
+.PP
+Absolute paths ensure the generated
+.I odbcinst.ini
+works with all ODBC driver managers (both unixODBC and iODBC) without
+hard\-coding architecture directories.
+Using bare names in snippets keeps them noarch\-portable: the same
+.I .ini
+file works on x86_64, aarch64, ppc64le, etc., because the generator
+handles path differences at generation time.
+.SH FILE CONFLICTS
+Each snippet filename must be unique within its directory.
+If two RPM packages ship the same file (e.g.\& both install
+.IR 30\-oracle.ini ),
+RPM will raise a file conflict and refuse to install both.
+.PP
+Third\-party packagers should choose unique names that include the
+driver\(aqs identity, for example:
+.PP
+.RS
+.nf
+30\-seerodbc.ini
+31\-oracle\-instantclient.ini
+32\-snowflake.ini
+.fi
+.RE
+.SH OUTPUT FILE
+.TP
+.I /etc/odbcinst.ini
+The generated ODBC driver configuration file.
+This file is marked as
+.B %ghost
+in the RPM package and should not be edited directly.
+Changes will be overwritten on the next regeneration.
+.SH EXIT STATUS
+.TP
+.B 0
+Success.
+.TP
+.B >0
+An error occurred (e.g.\& unable to write the output file).
+.SH EXAMPLES
+.SS Adding an administrator driver
+Create a snippet with a bare library name and regenerate:
+.PP
+.RS
+.nf
+cat > /etc/odbc/odbcinst.d/60\-mydriver.ini << 'EOF'
+[MyDriver]
+Driver = libmydriver.so
+EOF
+odbcinst\-generate
+.fi
+.RE
+.PP
+If
+.I libmydriver.so
+exists only in
+.IR /usr/lib64/odbc/ ,
+the generated
+.I /etc/odbcinst.ini
+will contain the resolved absolute path:
+.PP
+.RS
+.nf
+[MyDriver]
+Driver = /usr/lib64/odbc/libmydriver.so
+.fi
+.RE
+.PP
+If both 64\-bit and 32\-bit versions are installed, the output
+contains both keys:
+.PP
+.RS
+.nf
+[MyDriver]
+Driver   = /usr/lib/odbc/libmydriver.so
+Driver64 = /usr/lib64/odbc/libmydriver.so
+.fi
+.RE
+.PP
+If the library is not found in
+.I /usr/lib*/odbc/
+(e.g.\& it is installed in
+.IR /usr/lib64/ ),
+the bare name is kept as\-is.
+.PP
+.SS Vendor snippet (distro\-shipped)
+A Fedora driver package installs a snippet in the vendor directory:
+.PP
+.RS
+.nf
+# Shipped as /usr/lib/odbc/odbcinst.d/10\-mariadb.ini
+[MariaDB]
+Driver = libmaodbc.so
+.fi
+.RE
+.PP
+.SS Overriding a vendor snippet
+An administrator can override the MariaDB driver configuration
+without modifying the vendor file:
+.PP
+.RS
+.nf
+cat > /etc/odbc/odbcinst.d/50\-mariadb.ini << 'EOF'
+[MariaDB]
+Driver = libmaodbc.so
+Threading = 2
+EOF
+odbcinst\-generate
+.fi
+.RE
+.PP
+The administrator file wins because the admin directory always takes
+precedence over the vendor directory for the same stripped name.
+.SH NOTES
+.SS Comparison with other Fedora drop\-in systems
+Several Fedora packages use a similar dual\-directory drop\-in pattern:
+.BR sysctl.d (5),
+.BR tmpfiles.d (5),
+.BR modprobe.d (5),
+and
+.BR systemd.unit (5).
+Those systems use
+.B exact filename matching
+for overrides:
+.I /etc/sysctl.d/10\-foo.conf
+masks
+.I /usr/lib/sysctl.d/10\-foo.conf
+(same filename), but
+.I /etc/sysctl.d/60\-foo.conf
+and
+.I /usr/lib/sysctl.d/10\-foo.conf
+coexist as two separate files, with conflicting keys resolved
+individually by lexicographic sort order.
+.PP
+That model works for key\-value configurations where individual
+settings can be overridden independently.
+ODBC driver registration is structurally different: each snippet
+declares a complete
+.BI [ DriverName ]
+INI section, and merging individual keys from two
+.B [MariaDB]
+sections across files would be ambiguous and error\-prone.
+The entire section is the atomic unit.
+.PP
+.B odbcinst\-generate
+therefore uses
+.B stripped\-name matching
+instead: files that share the same name after removing the numeric
+prefix compete, and only one survives (see
+.BR "OVERRIDE SEMANTICS" ).
+The administrator directory always wins for the same stripped name,
+regardless of numeric prefix.
+This is a deliberate deviation from the
+.BR sysctl.d / tmpfiles.d
+model, chosen because INI section overrides must be whole\-section
+replacements.
+.SS Numeric range convention
+.BR sysctl.d (5)
+documents ranges 10\(en40 for vendor packages and 60\(en90 for
+local administration.
+This system adds a third\-party band (30\(en49) because out\-of\-tree
+ODBC drivers (COPR packages, vendor RPMs, third\-party connectors)
+are a common use case that does not fit the distro vendor or
+administrator categories.
+See
+.B FILE NAMING
+for the full range table.
+.SH AUTHORS
+Michal Schorm <mschorm@redhat.com>
+.SH COPYRIGHT
+Copyright \(co 2026 Michal Schorm.
+.br
+Licensed under the GNU General Public License, version 2 or later.
+.SH SEE ALSO
+.BR odbcinst (1),
+.BR odbcinst.ini (5),
+.BR odbc.ini (5),
+.BR unixODBC (7),
+.BR sysctl.d (5),
+.BR tmpfiles.d (5)

diff --git a/unixODBC.spec b/unixODBC.spec
index c9e9a55..47b6cfa 100644
--- a/unixODBC.spec
+++ b/unixODBC.spec
@@ -2,7 +2,7 @@
 
 Name:    unixODBC
 Version: 2.3.14
-Release: 10%{?dist}
+Release: 11%{?dist}
 
 # See README: Programs are GPL, libraries are LGPL
 # News Server library (Drivers/nn/yyparse.c) is GPLv3+
@@ -13,7 +13,8 @@ Summary: A complete ODBC driver manager for Linux
 URL:     http://www.unixODBC.org/
 
 Source:  http://www.unixODBC.org/%{name}-%{version}.tar.gz
-Source1: odbcinst.ini
+Source2: odbcinst-generate
+Source3: odbcinst-generate.1
 
 Patch8:  so-version-bump.patch
 
@@ -32,11 +33,21 @@ Suggests: sqliteodbc
 # GUI management tool
 Suggests: unixODBC-gui-qt
 
+Requires: odbcinst-generate = %{version}-%{release}
+
 %description
 Install unixODBC if you want to access databases through ODBC.
 You will also need the mariadb-connector-odbc package if you want to access
 a MySQL or MariaDB database, and/or the postgresql-odbc package for PostgreSQL.
 
+%package -n odbcinst-generate
+Summary: Drop-in snippet generator for ODBC driver registration
+BuildArch: noarch
+
+%description -n odbcinst-generate
+Assembles /etc/odbcinst.ini from per-driver drop-in snippets shipped by
+ODBC driver packages.  Used by both unixODBC and iODBC driver managers.
+
 %package devel
 Summary: Development files for programs which will use the unixODBC library
 Requires: %{name}%{?_isa} = %{version}-%{release}
@@ -76,7 +87,15 @@ sed -i 's|^runpath_var=LD_RUN_PATH|runpath_var=DIE_RPATH_DIE|g' libtool
 %install
 %make_install
 
-install -m644 %{SOURCE1} $RPM_BUILD_ROOT%{_sysconfdir}
+# Drop-in directory infrastructure for ODBC driver registration
+mkdir -p $RPM_BUILD_ROOT%{_prefix}/lib/odbc/odbcinst.d
+mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/odbc/odbcinst.d
+install -m755 %{SOURCE2} $RPM_BUILD_ROOT%{_bindir}/odbcinst-generate
+install -m644 -D %{SOURCE3} $RPM_BUILD_ROOT%{_mandir}/man1/odbcinst-generate.1
+
+# Create %ghost file so RPM tracks ownership of the generated config
+touch $RPM_BUILD_ROOT%{_sysconfdir}/odbcinst.ini
+
 %if %{undefined rhel}
 %multilib_fix_c_header --file %{_includedir}/unixODBC/unixodbc_conf.h
 %multilib_fix_c_header --file %{_includedir}/unixodbc.h
@@ -113,7 +132,7 @@ find $RPM_BUILD_ROOT%{_libdir} -name "*.so"   | sed "s|^$RPM_BUILD_ROOT||" > dev
 %doc doc
 %endif
 
-%config(noreplace) %{_sysconfdir}/odbc*
+%config(noreplace) %{_sysconfdir}/odbc.ini
 
 %{_bindir}/odbcinst
 %{_bindir}/isql
@@ -122,9 +141,103 @@ find $RPM_BUILD_ROOT%{_libdir} -name "*.so"   | sed "s|^$RPM_BUILD_ROOT||" > dev
 %{_bindir}/odbc_config
 %{_bindir}/slencheck
 %{_mandir}/man*/*
+%exclude %{_mandir}/man1/odbcinst-generate.1*
 # ODBC connector/driver plugins are placed here
 %dir %{_libdir}/odbc
 
+# Save user-modified odbcinst.ini before the %ghost transition removes it.
+# RPM does not create .rpmsave when a file changes from %config to %ghost.
+# Only run on first upgrade: skip if the drop-in directory already exists.
+#
+# To avoid nagging users who never customized the file, we parse it for
+# INI section headers: if every [Section] matches a driver shipped by Fedora,
+# the file is stock and needs no backup.  Any unknown section means the user
+# added a custom driver, so we preserve the file as .rpmsave.
+%pretrans -p <lua>
+local dropin = "/usr/lib/odbc/odbcinst.d"
+if posix.stat(dropin) then
+  return
+end
+local path = "/etc/odbcinst.ini"
+if not posix.stat(path) then
+  return
+end
+-- Drivers historically shipped by Fedora / RHEL in odbcinst.ini
+local known = {
+  PostgreSQL = true,
+  MySQL = true,
+  ["MySQL-5"] = true,
+  FreeTDS = true,
+  MariaDB = true,
+  MDBTools = true,
+  SQLITE = true,
+  SQLITE3 = true,
+}
+local custom = false
+local f = io.open(path, "r")
+if f then
+  for line in f:lines() do
+    -- Strip leading whitespace without Lua patterns (the "%%"
+    -- escape needed for Lua character classes conflicts with RPM
+    -- macro syntax in spec files, so we avoid patterns entirely).
+    local s = line
+    while s:sub(1,1) == " " or s:sub(1,1) == "\t" do
+      s = s:sub(2)
+    end
+    if s:sub(1,1) == "[" then
+      local close = s:find("]", 2, true)
+      if close and close > 2 then
+        local sect = s:sub(2, close - 1)
+        if not known[sect] then
+          custom = true
+          break
+        end
+      end
+    end
+  end
+  f:close()
+end
+-- Only save the file when it contains user-added driver sections
+if custom then
+  local save = path .. ".rpmsave"
+  if not posix.stat(save) then
+    os.rename(path, save)
+  end
+end
+
+%post
+if [ -f %{_sysconfdir}/odbcinst.ini.rpmsave ]; then
+  echo "NOTE: Your previous %{_sysconfdir}/odbcinst.ini was saved as" >&2
+  echo "%{_sysconfdir}/odbcinst.ini.rpmsave" >&2
+  echo "Driver registration is now handled by drop-in snippets." >&2
+  echo "To preserve custom drivers, copy their [sections] into files" >&2
+  echo "under %{_sysconfdir}/odbc/odbcinst.d/ and run:" >&2
+  echo "  odbcinst-generate" >&2
+fi
+
+
+%files -n odbcinst-generate
+%license exe/COPYING
+%{_bindir}/odbcinst-generate
+%{_mandir}/man1/odbcinst-generate.1*
+%ghost %{_sysconfdir}/odbcinst.ini
+%dir %{_prefix}/lib/odbc
+%dir %{_prefix}/lib/odbc/odbcinst.d
+%dir %{_sysconfdir}/odbc
+%dir %{_sysconfdir}/odbc/odbcinst.d
+
+%post -n odbcinst-generate
+odbcinst-generate
+
+# Regenerate odbcinst.ini when driver packages install drop-in snippets
+%transfiletriggerin -n odbcinst-generate -- %{_prefix}/lib/odbc/odbcinst.d %{_sysconfdir}/odbc/odbcinst.d
+odbcinst-generate
+
+# Regenerate after driver packages are removed
+%transfiletriggerpostun -n odbcinst-generate -- %{_prefix}/lib/odbc/odbcinst.d %{_sysconfdir}/odbc/odbcinst.d
+odbcinst-generate
+
+
 %files devel -f devel-so-list
 %{_includedir}/*
 %{_libdir}/pkgconfig/*.pc

                 reply	other threads:[~2026-07-29 11:11 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178532351199.1.9385421156362987254.rpms-unixODBC-2d0d3091432e@fedoraproject.org \
    --to=mschorm@redhat.com \
    --cc=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox