To determine whether an ELF archive contains a member of a particular type add a new input option to eu-classify --ar-member. eu-classify will match if a given file is an ELF archive and at 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-classify --ar-member --unstripped <ar-file> RFC: Is the option name --ar-member clear? Do we also want a variant (--all-ar-members) that checks whether all members match? Do we want this to work recursively? Should it print the member names that match instead of the archive file (is that useful?). * 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. FIXME: Add some testcases. --- src/elfclassify.c | 235 +++++++++++++++++++++++++++++++++------------- 1 file changed, 170 insertions(+), 65 deletions(-) diff --git a/src/elfclassify.c b/src/elfclassify.c index 25fe9a652ea4..779b065beb4b 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_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_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_ar_member: + flag_ar_member = true; + break; + case classify_flag_stdin: flag_stdin = do_stdin; break; @@ -735,79 +742,173 @@ 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; + current_path = malloc (path_size); + if (current_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; + } + current_path = new_path; + } + + if (sprintf (current_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. */ + 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 (current_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_ar_member && run_classify () && is_elf_archive ()) + checks_passed = check_ar_members (); + else if (elf_opened && !flag_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 +1041,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 }, + { "ar-member", classify_flag_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 +1084,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 --ar-member is given the file must be an ELF \ +archive and the classification options most 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 \ -- 2.50.1