To determine whether an ELF archive contains a member of a particular
type add a new input option to eu-elfclassify --any-ar-member.
eu-elfclassify will match if a given file is an ELF archive and the
classification options apply to at least one of the members.

This can be used for example to check whether an ELF archive file
contains ELF members that can be stripped by using:

  eu-elfclassify --ar-member --unstripped <ar-file>

        * src/elfclassify.c (current_path): Drop const.
        (flag_ar_member): New static bool.
        (classify_flag_ar_member): new enum member.
        (parse_opt): Handle classify_flag_ar_member.
        (check_checks): New function taken from...
        (process_current_path): ...here.
        (check_ar_members): New function.
        (main): Add ar-member to options. Update argp.doc.
        * tests/run-elfclassify.sh: Add various --any-ar-member tests.

Signed-off-by: Mark Wielaard <m...@klomp.org>
---
 src/elfclassify.c        | 236 ++++++++++++++++++++++++++++-----------
 tests/run-elfclassify.sh |  27 +++++
 2 files changed, 198 insertions(+), 65 deletions(-)

diff --git a/src/elfclassify.c b/src/elfclassify.c
index 25fe9a652ea4..311530d74dd8 100644
--- a/src/elfclassify.c
+++ b/src/elfclassify.c
@@ -1,5 +1,6 @@
 /* Classification of ELF files.
    Copyright (C) 2019 Red Hat, Inc.
+   Copyright (C) 2025 Mark J. Wielaard <m...@klomp.org>
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -42,8 +43,8 @@ ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
 /* Set by parse_opt.  */
 static int verbose;
 
-/* Set by the main function.  */
-static const char *current_path;
+/* Set by the main function and check_ar_members.  */
+static char *current_path;
 
 /* Set by open_file.  */
 static int file_fd = -1;
@@ -76,6 +77,7 @@ elf_issue (const char *msg)
 
 /* Set by parse_opt.  */
 static bool flag_only_regular_files;
+static bool flag_any_ar_member;
 
 static bool
 open_file (void)
@@ -647,6 +649,7 @@ enum
   classify_flag_no_print,
   classify_flag_matching,
   classify_flag_not_matching,
+  classify_flag_any_ar_member
 };
 
 static bool
@@ -696,6 +699,10 @@ parse_opt (int key, char *arg __attribute__ ((unused)),
        flag_only_regular_files = true;
        break;
 
+      case classify_flag_any_ar_member:
+       flag_any_ar_member = true;
+       break;
+
       case classify_flag_stdin:
         flag_stdin = do_stdin;
         break;
@@ -735,79 +742,174 @@ parse_opt (int key, char *arg __attribute__ ((unused)),
   return 0;
 }
 
+static bool
+check_checks ()
+{
+  bool checks_passed = true;
+  bool checks[] =
+    {
+      [classify_elf] = is_elf (),
+      [classify_elf_file] = is_elf_file (),
+      [classify_elf_archive] = is_elf_archive (),
+      [classify_core] = is_core (),
+      [classify_unstripped] = is_unstripped (),
+      [classify_executable] = is_executable (),
+      [classify_program] = is_program (),
+      [classify_shared] = is_shared (),
+      [classify_library] = is_library (),
+      [classify_linux_kernel_module] = is_linux_kernel_module (),
+      [classify_debug_only] = is_debug_only (),
+      [classify_loadable] = is_loadable (),
+    };
+
+  if (verbose > 1)
+    {
+      if (checks[classify_elf])
+       fprintf (stderr, "debug: %s: elf\n", current_path);
+      if (checks[classify_elf_file])
+       fprintf (stderr, "debug: %s: elf_file\n", current_path);
+      if (checks[classify_elf_archive])
+       fprintf (stderr, "debug: %s: elf_archive\n", current_path);
+      if (checks[classify_core])
+       fprintf (stderr, "debug: %s: core\n", current_path);
+      if (checks[classify_unstripped])
+       fprintf (stderr, "debug: %s: unstripped\n", current_path);
+      if (checks[classify_executable])
+       fprintf (stderr, "debug: %s: executable\n", current_path);
+      if (checks[classify_program])
+       fprintf (stderr, "debug: %s: program\n", current_path);
+      if (checks[classify_shared])
+       fprintf (stderr, "debug: %s: shared\n", current_path);
+      if (checks[classify_library])
+       fprintf (stderr, "debug: %s: library\n", current_path);
+      if (checks[classify_linux_kernel_module])
+       fprintf (stderr, "debug: %s: linux kernel module\n", current_path);
+      if (checks[classify_debug_only])
+       fprintf (stderr, "debug: %s: debug-only\n", current_path);
+      if (checks[classify_loadable])
+       fprintf (stderr, "debug: %s: loadable\n", current_path);
+    }
+
+  for (enum classify_check check = 0;
+       check <= classify_check_last; ++check)
+    switch (requirements[check])
+      {
+      case required:
+       if (!checks[check])
+         checks_passed = false;
+       break;
+      case forbidden:
+       if (checks[check])
+         checks_passed = false;
+       break;
+      case do_not_care:
+       break;
+      }
+
+  return checks_passed;
+}
+
+static bool
+check_ar_members ()
+{
+  char *ar_path = current_path;
+  Elf *ar_elf = elf;
+  bool checks_passed = false;
+
+  /* Guess some storage space for the "ar_path[member_path]" string so
+     we have to hopefully only allocate once.  */
+  size_t path_size = 2 * strlen (ar_path) + 24;
+  char *full_path = malloc (path_size);
+  if (full_path == NULL)
+    {
+      issue (ENOMEM, N_("allocating a member string name storage"));
+      current_path = ar_path;
+      return false;
+    }
+
+  int cmd = ELF_C_READ;
+  bool bad_ar = false;
+  while ((elf = elf_begin (file_fd, cmd, ar_elf)) != NULL)
+    {
+      Elf_Arhdr *arhdr = elf_getarhdr (elf);
+      if (arhdr == NULL)
+       {
+         elf_issue (N_("getting ar header"));
+         elf_end (elf);
+         bad_ar = true;
+         break;
+       }
+
+      char *ar_name = arhdr->ar_name ?: "<unknown>";
+      if (path_size < strlen (ar_path) + strlen (ar_name) + 3)
+       {
+         path_size = strlen (ar_path) + strlen (ar_name) + 24;
+         char *new_path = realloc (current_path, path_size);
+         if (new_path == NULL)
+           {
+             issue (ENOMEM, N_("allocating a member string name storage"));
+             elf_end (elf);
+             bad_ar = true;
+             break;
+           }
+         full_path = new_path;
+       }
+
+      if (sprintf (full_path, "%s[%s]", ar_path, ar_name) < 0)
+       {
+         issue (0, N_("constructing ar member string name"));
+         elf_end (elf);
+         bad_ar = true;
+         break;
+       }
+
+      /* One member (and no errors) is all it takes to pass.  But we
+        check all members to make sure it is a valid archive.  */
+      current_path = full_path;
+      if (run_classify () && check_checks ())
+       checks_passed = true;
+
+      cmd = elf_next (elf);
+
+      if (elf_end (elf) != 0)
+       {
+         elf_issue (N_("closing ar member"));
+         bad_ar = true;
+       }
+
+      if (bad_ar)
+       break;
+    }
+
+  if (bad_ar)
+    checks_passed = false;
+
+  elf = ar_elf;
+  free (full_path);
+  current_path = ar_path;
+  return checks_passed;
+}
+
 /* Perform requested checks against the file at current_path.  If
    necessary, sets *STATUS to 1 if checks failed.  */
 static void
 process_current_path (int *status)
 {
   bool checks_passed = true;
+  bool elf_opened = open_elf ();
 
-  if (open_elf () && run_classify ())
-    {
-      bool checks[] =
-        {
-        [classify_elf] = is_elf (),
-        [classify_elf_file] = is_elf_file (),
-        [classify_elf_archive] = is_elf_archive (),
-        [classify_core] = is_core (),
-        [classify_unstripped] = is_unstripped (),
-        [classify_executable] = is_executable (),
-        [classify_program] = is_program (),
-        [classify_shared] = is_shared (),
-        [classify_library] = is_library (),
-        [classify_linux_kernel_module] = is_linux_kernel_module (),
-        [classify_debug_only] = is_debug_only (),
-        [classify_loadable] = is_loadable (),
-       };
-
-      if (verbose > 1)
-        {
-         if (checks[classify_elf])
-           fprintf (stderr, "debug: %s: elf\n", current_path);
-         if (checks[classify_elf_file])
-           fprintf (stderr, "debug: %s: elf_file\n", current_path);
-         if (checks[classify_elf_archive])
-           fprintf (stderr, "debug: %s: elf_archive\n", current_path);
-         if (checks[classify_core])
-           fprintf (stderr, "debug: %s: core\n", current_path);
-          if (checks[classify_unstripped])
-            fprintf (stderr, "debug: %s: unstripped\n", current_path);
-          if (checks[classify_executable])
-            fprintf (stderr, "debug: %s: executable\n", current_path);
-          if (checks[classify_program])
-            fprintf (stderr, "debug: %s: program\n", current_path);
-          if (checks[classify_shared])
-            fprintf (stderr, "debug: %s: shared\n", current_path);
-          if (checks[classify_library])
-            fprintf (stderr, "debug: %s: library\n", current_path);
-         if (checks[classify_linux_kernel_module])
-           fprintf (stderr, "debug: %s: linux kernel module\n", current_path);
-         if (checks[classify_debug_only])
-           fprintf (stderr, "debug: %s: debug-only\n", current_path);
-          if (checks[classify_loadable])
-            fprintf (stderr, "debug: %s: loadable\n", current_path);
-        }
-
-      for (enum classify_check check = 0;
-           check <= classify_check_last; ++check)
-        switch (requirements[check])
-          {
-          case required:
-            if (!checks[check])
-              checks_passed = false;
-            break;
-          case forbidden:
-            if (checks[check])
-              checks_passed = false;
-            break;
-          case do_not_care:
-            break;
-          }
-    }
+  if (elf_opened && flag_any_ar_member && run_classify () && is_elf_archive ())
+    checks_passed = check_ar_members ();
+  else if (elf_opened && !flag_any_ar_member && run_classify ())
+    checks_passed = check_checks ();
   else if (file_fd == -1)
     checks_passed = false; /* There is nothing to check, bad file.  */
   else
     {
+      /* Not a bad file, but couldn't open it as an ELF file or trying
+        to run some classify tests on it produced an error.  Check if
+        there were any required tests (normally there is at least the
+        default --elf, but the user could have used --not-elf).  */
       for (enum classify_check check = 0;
            check <= classify_check_last; ++check)
         if (requirements[check] == required)
@@ -940,6 +1042,8 @@ and can be stripped further"), 1 },
       { NULL, 0, NULL, OPTION_DOC, N_("Input flags"), 2 },
       { "file", 'f', NULL, 0,
         N_("Only classify regular (not symlink nor special device) files"), 2 
},
+      { "any-ar-member", classify_flag_any_ar_member, NULL, 0,
+        N_("Input is an ar file, classification options apply to ar member"), 
2 },
       { "stdin", classify_flag_stdin, NULL, 0,
         N_("Also read file names to process from standard input, \
 separated by newlines"), 2 },
@@ -981,7 +1085,9 @@ separated by ASCII NUL bytes"), 2 },
 Determine the type of an ELF file.\
 \n\n\
 All of the classification options must apply at the same time to a \
-particular file.  Classification options can be negated using a \
+particular file.  Or if --any-ar-member is given the file must be an \
+ELF archive and the classification options must apply to at least one \
+archive member.  Classification options can be negated using a \
 \"--not-\" prefix.\
 \n\n\
 Since modern ELF does not clearly distinguish between programs and \
diff --git a/tests/run-elfclassify.sh b/tests/run-elfclassify.sh
index fb14139d81bf..a9b4ad764c02 100755
--- a/tests/run-elfclassify.sh
+++ b/tests/run-elfclassify.sh
@@ -325,3 +325,30 @@ testrun ${abs_top_builddir}/src/elfclassify --debug-only 
$debug_files
 testrun_compare ${abs_top_builddir}/src/elfclassify --debug-only --print 
$debug_files <<EOF
 $(echo $debug_files | sed -e "s/ /\n/g")
 EOF
+
+testfiles testarchive64.a test-ar-duplicates.a
+
+echo "any-ar-member unstripped"
+# Both archives contain object files with symtabs
+testrun_compare ${abs_top_builddir}/src/elfclassify --any-ar-member 
--unstripped --print testarchive64.a test-ar-duplicates.a <<EOF
+testarchive64.a
+test-ar-duplicates.a
+EOF
+
+echo "any-ar-member not-unstripped"
+# Empty (opposite of above)
+testrun_compare ${abs_top_builddir}/src/elfclassify --any-ar-member 
--not-unstripped --print testarchive64.a test-ar-duplicates.a <<EOF
+EOF
+
+echo "any-ar-member executable"
+# Only test-ar-duplicates.a contains executables
+testrun_compare ${abs_top_builddir}/src/elfclassify --any-ar-member 
--executable --print testarchive64.a test-ar-duplicates.a <<EOF
+test-ar-duplicates.a
+EOF
+
+echo "any-ar-member not-executable"
+# All members in test-ar-duplicates.a are executables
+# So only testarchive64.a has no-executable member(s)
+testrun_compare ${abs_top_builddir}/src/elfclassify --any-ar-member 
--not-executable --print testarchive64.a test-ar-duplicates.a <<EOF
+testarchive64.a
+EOF
-- 
2.50.1

Reply via email to