From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Robert Marshall Date: Mon, 16 Mar 2015 14:14:19 -0400 Subject: [PATCH] Use Distribution Package Sort for grub2-mkconfig (#1124074) Users reported that newly installed kernels on their systems installed with grub-mkconfig would not appear on the grub boot list in order starting with the most recent. Added an option for rpm-based systems to use the rpm-sort library to sort kernels instead. Resolves rhbz#1124074 Signed-off-by: Robert Marshall [pjones: fix --enable-rpm-sort configure option] Signed-off-by: Peter Jones [thierry.vignaud: fix build with rpm-4.16] Signed-off-by: Thierry Vignaud --- configure.ac | 38 +++++++ Makefile.util.def | 16 +++ util/grub-rpm-sort.c | 281 ++++++++++++++++++++++++++++++++++++++++++++++ util/grub-mkconfig_lib.in | 11 +- util/grub-rpm-sort.8 | 12 ++ 5 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 util/grub-rpm-sort.c create mode 100644 util/grub-rpm-sort.8 diff --git a/configure.ac b/configure.ac index bec8535af70..fdcb452581c 100644 --- a/configure.ac +++ b/configure.ac @@ -72,6 +72,7 @@ grub_TRANSFORM([grub-mkrelpath]) grub_TRANSFORM([grub-mkrescue]) grub_TRANSFORM([grub-probe]) grub_TRANSFORM([grub-reboot]) +grub_TRANSFORM([grub-rpm-sort]) grub_TRANSFORM([grub-script-check]) grub_TRANSFORM([grub-set-default]) grub_TRANSFORM([grub-sparc64-setup]) @@ -95,6 +96,7 @@ grub_TRANSFORM([grub-mkrescue.1]) grub_TRANSFORM([grub-mkstandalone.3]) grub_TRANSFORM([grub-ofpathname.3]) grub_TRANSFORM([grub-probe.3]) +grub_TRANSFORM([grub-rpm-sort.8]) grub_TRANSFORM([grub-reboot.3]) grub_TRANSFORM([grub-render-label.3]) grub_TRANSFORM([grub-script-check.3]) @@ -1860,6 +1862,42 @@ fi AC_SUBST([LIBDEVMAPPER]) +AC_ARG_ENABLE([rpm-sort], + [AS_HELP_STRING([--enable-rpm-sort], + [enable native rpm sorting of kernels in grub (default=guessed)])]) +if test x"$enable_rpm_sort" = xno ; then + rpm_sort_excuse="explicitly disabled" +fi + +if test x"$rpm_sort_excuse" = x ; then + # Check for rpmlib header. + AC_CHECK_HEADER([rpm/rpmlib.h], [], + [rpm_sort_excuse="need rpm/rpmlib header"]) +fi + +if test x"$rpm_sort_excuse" = x ; then + # Check for rpm library. + AC_CHECK_LIB([rpm], [rpmvercmp], [], + [rpm_sort_excuse="rpmlib missing rpmvercmp"]) +fi + +if test x"$rpm_sort_excuse" = x ; then + LIBRPM="-lrpm"; + AC_DEFINE([HAVE_RPM], [1], + [Define to 1 if you have the rpm library.]) +fi + +if test x"$LIBRPM" = x ; then + # Check for rpm library. + AC_CHECK_LIB([rpmio], [rpmvercmp], [], + [rpm_sort_excuse="rpmio missing rpmvercmp"]) + LIBRPM="-lrpmio"; + AC_DEFINE([HAVE_RPMIO], [1], + [Define to 1 if you have the rpm library.]) +fi + +AC_SUBST([LIBRPM]) + LIBGEOM= if test x$host_kernel = xkfreebsd; then AC_CHECK_LIB([geom], [geom_gettree], [], diff --git a/Makefile.util.def b/Makefile.util.def index 2c9b283a230..ba4cf4b29b0 100644 --- a/Makefile.util.def +++ b/Makefile.util.def @@ -703,6 +703,22 @@ program = { ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)'; }; +program = { + name = grub-rpm-sort; + mansection = 8; + installdir = sbin; + + common = grub-core/kern/emu/misc.c; + common = grub-core/kern/emu/argp_common.c; + common = grub-core/osdep/init.c; + common = util/misc.c; + common = util/grub-rpm-sort.c; + + ldadd = libgrubkern.a; + ldadd = grub-core/lib/gnulib/libgnu.a; + ldadd = '$(LIBDEVMAPPER) $(LIBRPM)'; +}; + script = { name = grub-mkconfig; common = util/grub-mkconfig.in; diff --git a/util/grub-rpm-sort.c b/util/grub-rpm-sort.c new file mode 100644 index 00000000000..f33bd1ed568 --- /dev/null +++ b/util/grub-rpm-sort.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static size_t +read_file (const char *input, char **ret) +{ + FILE *in; + size_t s; + size_t sz = 2048; + size_t offset = 0; + char *text; + + if (!strcmp(input, "-")) + in = stdin; + else + in = grub_util_fopen(input, "r"); + + text = xmalloc (sz); + + if (!in) + grub_util_error (_("cannot open `%s': %s"), input, strerror (errno)); + + while ((s = fread (text + offset, 1, sz - offset, in)) != 0) + { + offset += s; + if (sz - offset == 0) + { + sz += 2048; + text = xrealloc (text, sz); + } + } + + text[offset] = '\0'; + *ret = text; + + if (in != stdin) + fclose(in); + + return offset + 1; +} + +/* returns name/version/release */ +/* NULL string pointer returned if nothing found */ +static void +split_package_string (char *package_string, char **name, + char **version, char **release) +{ + char *package_version, *package_release; + + /* Release */ + package_release = strrchr (package_string, '-'); + + if (package_release != NULL) + *package_release++ = '\0'; + + *release = package_release; + + /* Version */ + package_version = strrchr(package_string, '-'); + + if (package_version != NULL) + *package_version++ = '\0'; + + *version = package_version; + /* Name */ + *name = package_string; + + /* Bubble up non-null values from release to name */ + if (*name == NULL) + { + *name = (*version == NULL ? *release : *version); + *version = *release; + *release = NULL; + } + if (*version == NULL) + { + *version = *release; + *release = NULL; + } +} + +/* + * package name-version-release comparator for qsort + * expects p, q which are pointers to character strings (char *) + * which will not be altered in this function + */ +static int +package_version_compare (const void *p, const void *q) +{ + char *local_p, *local_q; + char *lhs_name, *lhs_version, *lhs_release; + char *rhs_name, *rhs_version, *rhs_release; + int vercmpflag = 0; + + local_p = alloca (strlen (*(char * const *)p) + 1); + local_q = alloca (strlen (*(char * const *)q) + 1); + + /* make sure these allocated */ + assert (local_p); + assert (local_q); + + strcpy (local_p, *(char * const *)p); + strcpy (local_q, *(char * const *)q); + + split_package_string (local_p, &lhs_name, &lhs_version, &lhs_release); + split_package_string (local_q, &rhs_name, &rhs_version, &rhs_release); + + /* Check Name and return if unequal */ + vercmpflag = rpmvercmp ((lhs_name == NULL ? "" : lhs_name), + (rhs_name == NULL ? "" : rhs_name)); + if (vercmpflag != 0) + return vercmpflag; + + /* Check version and return if unequal */ + vercmpflag = rpmvercmp ((lhs_version == NULL ? "" : lhs_version), + (rhs_version == NULL ? "" : rhs_version)); + if (vercmpflag != 0) + return vercmpflag; + + /* Check release and return the version compare value */ + vercmpflag = rpmvercmp ((lhs_release == NULL ? "" : lhs_release), + (rhs_release == NULL ? "" : rhs_release)); + + return vercmpflag; +} + +static void +add_input (const char *filename, char ***package_names, size_t *n_package_names) +{ + char *orig_input_buffer = NULL; + char *input_buffer; + char *position_of_newline; + char **names = *package_names; + char **new_names = NULL; + size_t n_names = *n_package_names; + + if (!*package_names) + new_names = names = xmalloc (sizeof (char *) * 2); + + if (read_file (filename, &orig_input_buffer) < 2) + { + if (new_names) + free (new_names); + if (orig_input_buffer) + free (orig_input_buffer); + return; + } + + input_buffer = orig_input_buffer; + while (input_buffer && *input_buffer && + (position_of_newline = strchrnul (input_buffer, '\n'))) + { + size_t sz = position_of_newline - input_buffer; + char *new; + + if (sz == 0) + { + input_buffer = position_of_newline + 1; + continue; + } + + new = xmalloc (sz+1); + strncpy (new, input_buffer, sz); + new[sz] = '\0'; + + names = xrealloc (names, sizeof (char *) * (n_names + 1)); + names[n_names] = new; + n_names++; + + /* move buffer ahead to next line */ + input_buffer = position_of_newline + 1; + if (*position_of_newline == '\0') + input_buffer = NULL; + } + + free (orig_input_buffer); + + *package_names = names; + *n_package_names = n_names; +} + +static char * +help_filter (int key, const char *text, void *input __attribute__ ((unused))) +{ + return (char *)text; +} + +static struct argp_option options[] = { + { 0, } +}; + +struct arguments +{ + size_t ninputs; + size_t input_max; + char **inputs; +}; + +static error_t +argp_parser (int key, char *arg, struct argp_state *state) +{ + struct arguments *arguments = state->input; + switch (key) + { + case ARGP_KEY_ARG: + assert (arguments->ninputs < arguments->input_max); + arguments->inputs[arguments->ninputs++] = xstrdup (arg); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static struct argp argp = { + options, argp_parser, N_("[INPUT_FILES]"), + N_("Sort a list of strings in RPM version sort order."), + NULL, help_filter, NULL +}; + +int +main (int argc, char *argv[]) +{ + struct arguments arguments; + char **package_names = NULL; + size_t n_package_names = 0; + int i; + + grub_util_host_init (&argc, &argv); + + memset (&arguments, 0, sizeof (struct arguments)); + arguments.input_max = argc+1; + arguments.inputs = xmalloc ((arguments.input_max + 1) + * sizeof (arguments.inputs[0])); + memset (arguments.inputs, 0, (arguments.input_max + 1) + * sizeof (arguments.inputs[0])); + + /* Parse our arguments */ + if (argp_parse (&argp, argc, argv, 0, 0, &arguments) != 0) + grub_util_error ("%s", _("Error in parsing command line arguments\n")); + + /* If there's no inputs in argv, add one for stdin */ + if (!arguments.ninputs) + { + arguments.ninputs = 1; + arguments.inputs[0] = xmalloc (2); + strcpy(arguments.inputs[0], "-"); + } + + for (i = 0; i < arguments.ninputs; i++) + add_input(arguments.inputs[i], &package_names, &n_package_names); + + if (package_names == NULL || n_package_names < 1) + grub_util_error ("%s", _("Invalid input\n")); + + qsort (package_names, n_package_names, sizeof (char *), + package_version_compare); + + /* send sorted list to stdout */ + for (i = 0; i < n_package_names; i++) + { + fprintf (stdout, "%s\n", package_names[i]); + free (package_names[i]); + } + + free (package_names); + for (i = 0; i < arguments.ninputs; i++) + free (arguments.inputs[i]); + + free (arguments.inputs); + + return 0; +} diff --git a/util/grub-mkconfig_lib.in b/util/grub-mkconfig_lib.in index 0f6505bf3b6..42c2ea9ba50 100644 --- a/util/grub-mkconfig_lib.in +++ b/util/grub-mkconfig_lib.in @@ -33,6 +33,9 @@ fi if test "x$grub_mkrelpath" = x; then grub_mkrelpath="${bindir}/@grub_mkrelpath@" fi +if test "x$grub_rpm_sort" = x; then + grub_rpm_sort="${sbindir}/@grub_rpm_sort@" +fi if command -v gettext >/dev/null; then : @@ -218,6 +221,12 @@ version_sort () esac } +if [ "x$grub_rpm_sort" != x -a -x "$grub_rpm_sort" ]; then + kernel_sort="$grub_rpm_sort" +else + kernel_sort=version_sort +fi + version_test_numeric () { version_test_numeric_a="$1" @@ -234,7 +243,7 @@ version_test_numeric () version_test_numeric_a="$version_test_numeric_b" version_test_numeric_b="$version_test_numeric_c" fi - if (echo "$version_test_numeric_a" ; echo "$version_test_numeric_b") | version_sort | head -n 1 | grep -qx "$version_test_numeric_b" ; then + if (echo "$version_test_numeric_a" ; echo "$version_test_numeric_b") | "$kernel_sort" | head -n 1 | grep -qx "$version_test_numeric_b" ; then return 0 else return 1 diff --git a/util/grub-rpm-sort.8 b/util/grub-rpm-sort.8 new file mode 100644 index 00000000000..8ce21488448 --- /dev/null +++ b/util/grub-rpm-sort.8 @@ -0,0 +1,12 @@ +.TH GRUB-RPM-SORT 8 "Wed Feb 26 2014" +.SH NAME +\fBgrub-rpm-sort\fR \(em Sort input according to RPM version compare. + +.SH SYNOPSIS +\fBgrub-rpm-sort\fR [OPTIONS]. + +.SH DESCRIPTION +You should not normally run this program directly. Use grub-mkconfig instead. + +.SH SEE ALSO +.BR "info grub"