glibc_post_upgrade: Integrate into the build process

This gives us access to the relevant definitions and also enables
us to perform a static PIE build without replicate the entire
compiler invocation.

Due to the move into the glibc build process, the program had to
be cleaned up to compile without warnings.
This commit is contained in:
Florian Weimer 2018-01-19 16:32:18 +01:00
parent 34f077631c
commit 737f7e8513
2 changed files with 366 additions and 17 deletions

360
glibc-post_upgrade.patch Normal file
View File

@ -0,0 +1,360 @@
A helper program is needed to clean up the system configuration
early during RPM package installation, so that other scriptlets
can run successfully.
diff --git a/elf/Makefile b/elf/Makefile
index 2a432d8beebcd207..368dcae477fff2ae 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -117,6 +117,14 @@ others-extras = $(ldconfig-modules)
endif
endif
+# This needs to be statically linked because it is executed at a time
+# when there might be incompatible shared objects on disk, and the
+# purpose of this program is to remove them (among other things).
+others-static += glibc_post_upgrade
+others += glibc_post_upgrade
+glibc_post_upgrade-modules := static-stubs
+CFLAGS-glibc_post_upgrade.c += -DGCONV_MODULES_DIR='"$(gconvdir)"'
+
# To find xmalloc.c and xstrdup.c
vpath %.c ../locale/programs
@@ -559,6 +567,8 @@ $(objpfx)sln: $(sln-modules:%=$(objpfx)%.o)
$(objpfx)ldconfig: $(ldconfig-modules:%=$(objpfx)%.o)
+$(objpfx)glibc_post_upgrade: $(glibc_post_upgrade-modules:%=$(objpfx)%.o)
+
SYSCONF-FLAGS := -D'SYSCONFDIR="$(sysconfdir)"'
CFLAGS-ldconfig.c += $(SYSCONF-FLAGS) -D'LIBDIR="$(libdir)"' \
-D'SLIBDIR="$(slibdir)"'
diff --git a/elf/glibc_post_upgrade.c b/elf/glibc_post_upgrade.c
new file mode 100644
index 0000000000000000..3c9839ae523d2cc7
--- /dev/null
+++ b/elf/glibc_post_upgrade.c
@@ -0,0 +1,322 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <elf.h>
+
+#define LD_SO_CONF "/etc/ld.so.conf"
+#define ICONVCONFIG "/usr/sbin/iconvconfig"
+
+#define verbose_exec(failcode, path...) \
+ do \
+ { \
+ char *const arr[] = { path, NULL }; \
+ vexec (failcode, arr); \
+ } while (0)
+
+__attribute__((noinline)) static void vexec (int failcode, char *const path[]);
+__attribute__((noinline)) static void says (const char *str);
+__attribute__((noinline)) static void sayn (long num);
+__attribute__((noinline)) static void message (char *const path[]);
+__attribute__((noinline)) static int check_elf (const char *name);
+
+int
+main (void)
+{
+ struct stat statbuf;
+ char initpath[256];
+
+ char buffer[4096];
+ struct pref {
+ const char *p;
+ int len;
+ } prefix[] = { { "libc-", 5 }, { "libm-", 5 },
+ { "librt-", 6 }, { "libpthread-", 11 },
+ { "librtkaio-", 10 }, { "libthread_db-", 13 } };
+ int i, j, fd;
+ off_t base;
+ ssize_t ret;
+
+ /* In order to support in-place upgrades, we must immediately remove
+ obsolete platform directories after installing a new glibc
+ version. RPM only deletes files removed by updates near the end
+ of the transaction. If we did not remove the obsolete platform
+ directories here, they would be preferred by the dynamic linker
+ during the execution of subsequent RPM scriptlets, likely
+ resulting in process startup failures. */
+ const char *remove_dirs[] =
+ {
+#if defined (__i386__)
+ "/lib/i686",
+ "/lib/i686/nosegneg",
+#elif defined (__powerpc64__) && _CALL_ELF != 2
+ "/lib64/power6",
+#endif
+ };
+ for (j = 0; j < sizeof (remove_dirs) / sizeof (remove_dirs[0]); ++j)
+ {
+ size_t rmlen = strlen (remove_dirs[j]);
+ fd = open (remove_dirs[j], O_RDONLY);
+ if (fd >= 0
+ && (ret = getdirentries (fd, buffer, sizeof (buffer), &base))
+ >= (ssize_t) offsetof (struct dirent, d_name))
+ {
+ for (base = 0; base + offsetof (struct dirent, d_name) < ret; )
+ {
+ struct dirent *d = (struct dirent *) (buffer + base);
+
+ for (i = 0; i < sizeof (prefix) / sizeof (prefix[0]); i++)
+ if (! strncmp (d->d_name, prefix[i].p, prefix[i].len))
+ {
+ char *p = d->d_name + prefix[i].len;
+
+ while (*p == '.' || (*p >= '0' && *p <= '9')) p++;
+ if (p[0] == 's' && p[1] == 'o' && p[2] == '\0'
+ && p + 3 - d->d_name
+ < sizeof (initpath) - rmlen - 1)
+ {
+ memcpy (initpath, remove_dirs[j], rmlen);
+ initpath[rmlen] = '/';
+ strcpy (initpath + rmlen + 1, d->d_name);
+ unlink (initpath);
+ break;
+ }
+ }
+ base += d->d_reclen;
+ }
+ close (fd);
+ }
+ }
+
+ int ldsocfd = open (LD_SO_CONF, O_RDONLY);
+ struct stat ldsocst;
+ if (ldsocfd >= 0 && fstat (ldsocfd, &ldsocst) >= 0)
+ {
+ char p[ldsocst.st_size + 1];
+ if (read (ldsocfd, p, ldsocst.st_size) == ldsocst.st_size)
+ {
+ p[ldsocst.st_size] = '\0';
+ if (strstr (p, "include ld.so.conf.d/*.conf") == NULL)
+ {
+ close (ldsocfd);
+ ldsocfd = open (LD_SO_CONF, O_WRONLY | O_TRUNC);
+ if (ldsocfd >= 0)
+ {
+ size_t slen = strlen ("include ld.so.conf.d/*.conf\n");
+ if (write (ldsocfd, "include ld.so.conf.d/*.conf\n", slen)
+ != slen
+ || write (ldsocfd, p, ldsocst.st_size) != ldsocst.st_size)
+ _exit (109);
+ }
+ }
+ }
+ if (ldsocfd >= 0)
+ close (ldsocfd);
+ }
+
+ /* If installing bi-arch glibc, rpm sometimes doesn't unpack all files
+ before running one of the lib's %post scriptlet. /sbin/ldconfig will
+ then be run by the other arch's %post. */
+ if (! access ("/sbin/ldconfig", X_OK))
+ verbose_exec (110,
+ (char *) "/sbin/ldconfig",
+ (char *) "/sbin/ldconfig");
+
+ if (! utimes (GCONV_MODULES_DIR "/gconv-modules.cache", NULL))
+ {
+ const char *iconv_cache = GCONV_MODULES_DIR "/gconv-modules.cache";
+ const char *iconv_dir = GCONV_MODULES_DIR;
+ verbose_exec (113,
+ (char *) ICONVCONFIG,
+ (char *) "/usr/sbin/iconvconfig",
+ (char *) "-o",
+ (char *) iconv_cache,
+ (char *) "--nostdlib",
+ (char *) iconv_dir);
+ }
+
+ /* Check if telinit is available and either SysVInit fifo,
+ or upstart telinit. */
+ if (access ("/sbin/telinit", X_OK)
+ || ((!!access ("/dev/initctl", F_OK))
+ ^ !access ("/sbin/initctl", X_OK)))
+ _exit (0);
+
+ /* Check if we are not inside of some chroot, because we'd just
+ timeout and leave /etc/initrunlvl.
+
+ On more modern systems this test is not sufficient to detect
+ if we're in a chroot. */
+ if (readlink ("/proc/1/exe", initpath, 256) <= 0 ||
+ readlink ("/proc/1/root", initpath, 256) <= 0)
+ _exit (0);
+
+ /* Here's another well known way to detect chroot, at least on an
+ ext and xfs filesystems and assuming nothing mounted on the chroot's
+ root. */
+ if (stat ("/", &statbuf) != 0
+ || (statbuf.st_ino != 2
+ && statbuf.st_ino != 128))
+ _exit (0);
+
+ if (check_elf ("/proc/1/exe"))
+ verbose_exec (116,
+ (char *) "/sbin/telinit",
+ (char *) "/sbin/telinit",
+ (char *) "u");
+
+ /* Check if we can safely condrestart sshd. */
+ if (access ("/sbin/service", X_OK) == 0
+ && access ("/usr/sbin/sshd", X_OK) == 0
+ && access ("/etc/rc.d/init.d/sshd", X_OK) == 0
+ && access ("/bin/bash", X_OK) == 0)
+ {
+ if (check_elf ("/usr/sbin/sshd"))
+ verbose_exec (-121,
+ (char *) "/sbin/service",
+ (char *) "/sbin/service",
+ (char *) "sshd",
+ (char *) "condrestart");
+ }
+
+ _exit(0);
+}
+
+void
+vexec (int failcode, char *const path[])
+{
+ pid_t pid;
+ int status, save_errno;
+ int devnull = 0;
+
+ if (failcode < 0)
+ {
+ devnull = 1;
+ failcode = -failcode;
+ }
+ pid = vfork ();
+ if (pid == 0)
+ {
+ int fd;
+ if (devnull && (fd = open ("/dev/null", O_WRONLY)) >= 0)
+ {
+ dup2 (fd, 1);
+ dup2 (fd, 2);
+ close (fd);
+ }
+ execv (path[0], path + 1);
+ save_errno = errno;
+ message (path);
+ says (" exec failed with errno ");
+ sayn (save_errno);
+ says ("\n");
+ _exit (failcode);
+ }
+ else if (pid < 0)
+ {
+ save_errno = errno;
+ message (path);
+ says (" fork failed with errno ");
+ sayn (save_errno);
+ says ("\n");
+ _exit (failcode + 1);
+ }
+ if (waitpid (0, &status, 0) != pid || !WIFEXITED (status))
+ {
+ message (path);
+ says (" child terminated abnormally\n");
+ _exit (failcode + 2);
+ }
+ if (WEXITSTATUS (status))
+ {
+ message (path);
+ says (" child exited with exit code ");
+ sayn (WEXITSTATUS (status));
+ says ("\n");
+ _exit (WEXITSTATUS (status));
+ }
+}
+
+static void
+says (const char *str)
+{
+ write (1, str, strlen (str));
+}
+
+static void
+sayn (long num)
+{
+ char string[sizeof (long) * 3 + 1];
+ char *p = string + sizeof (string) - 1;
+
+ *p = '\0';
+ if (num == 0)
+ *--p = '0';
+ else
+ while (num)
+ {
+ *--p = '0' + num % 10;
+ num = num / 10;
+ }
+
+ says (p);
+}
+
+static void
+message (char *const path[])
+{
+ says ("/usr/sbin/glibc_post_upgrade: While trying to execute ");
+ says (path[0]);
+}
+
+static int
+check_elf (const char *name)
+{
+ /* Play safe, if we can't open or read, assume it might be
+ ELF for the current arch. */
+ int ret = 1;
+ int fd = open (name, O_RDONLY);
+ if (fd >= 0)
+ {
+ Elf32_Ehdr ehdr;
+ if (read (fd, &ehdr, offsetof (Elf32_Ehdr, e_version))
+ == offsetof (Elf32_Ehdr, e_version))
+ {
+ ret = 0;
+ if (ehdr.e_ident[EI_CLASS]
+ == (sizeof (long) == 8 ? ELFCLASS64 : ELFCLASS32))
+ {
+#if defined __i386__
+ ret = ehdr.e_machine == EM_386;
+#elif defined __x86_64__
+ ret = ehdr.e_machine == EM_X86_64;
+#elif defined __powerpc64__
+ ret = ehdr.e_machine == EM_PPC64;
+#elif defined __powerpc__
+ ret = ehdr.e_machine == EM_PPC;
+#elif defined __s390__ || defined __s390x__
+ ret = ehdr.e_machine == EM_S390;
+#elif defined __x86_64__
+ ret = ehdr.e_machine == EM_X86_64;
+#elif defined __sparc__
+ if (sizeof (long) == 8)
+ ret = ehdr.e_machine == EM_SPARCV9;
+ else
+ ret = (ehdr.e_machine == EM_SPARC
+ || ehdr.e_machine == EM_SPARC32PLUS);
+#else
+ ret = 1;
+#endif
+ }
+ }
+ close (fd);
+ }
+ return ret;
+}

View File

@ -118,7 +118,6 @@ License: LGPLv2+ and LGPLv2+ with exceptions and GPLv2+
URL: http://www.gnu.org/software/glibc/
Source0: %{?glibc_release_url}%{glibcsrcdir}.tar.gz
Source1: build-locale-archive.c
Source2: glibc_post_upgrade.c
Source4: nscd.conf
Source7: nsswitch.conf
Source8: power6emul.c
@ -154,7 +153,8 @@ Source11: SUPPORTED
#
##############################################################################
Patch0001: glibc-fedora-nscd.patch
Patch1: glibc-post_upgrade.patch
Patch2: glibc-fedora-nscd.patch
# All these were from the glibc-fedora.patch mega-patch and need another
# round of reviewing. Ideally they'll either be submitted upstream or
@ -734,7 +734,8 @@ microbenchmark tests on the system.
%setup -q -n %{glibcsrcdir}
# Patch order matters.
%patch0001 -p1
%patch1 -p1
%patch2 -p1
%patch2007 -p1
%patch0012 -p1
%patch2013 -p1
@ -999,18 +1000,6 @@ build
)
%endif
##############################################################################
# Build the glibc post-upgrade program:
# We only build one of these with the default set of options. This program
# must be able to run on all hardware for the lowest common denomintor since
# we only build it once.
##############################################################################
pushd build-%{target}
$GCC -static -L. -Os -g %{SOURCE2} \
-o glibc_post_upgrade.%{_target_cpu} \
'-DGCONV_MODULES_DIR="%{_libdir}/gconv"'
popd
##############################################################################
# Install glibc...
##############################################################################
@ -1245,7 +1234,7 @@ chmod 644 $RPM_BUILD_ROOT%{_libdir}/gconv/gconv-modules.cache
##############################################################################
# Install the upgrade program
install -m 700 build-%{target}/glibc_post_upgrade.%{_target_cpu} \
install -m 700 build-%{target}/elf/glibc_post_upgrade \
$RPM_BUILD_ROOT%{_prefix}/sbin/glibc_post_upgrade.%{_target_cpu}
# Strip all of the installed object files.
@ -1494,7 +1483,7 @@ rm -rf $RPM_BUILD_ROOT%{_prefix}/share/zoneinfo
# doesn't seem to be any macro to give us that. So we do the next best thing,
# which is to at least keep the timestamp consistent. The choice of using
# glibc_post_upgrade.c is arbitrary.
touch -r %{SOURCE2} $RPM_BUILD_ROOT/etc/ld.so.conf
touch -r %{SOURCE0} $RPM_BUILD_ROOT/etc/ld.so.conf
touch -r sunrpc/etc.rpc $RPM_BUILD_ROOT/etc/rpc
pushd build-%{target}