| /* bucomm.c -- Bin Utils COMmon code. | |
| Copyright (C) 1991-2024 Free Software Foundation, Inc. | |
| This file is part of GNU Binutils. | |
| This program is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program; if not, write to the Free Software | |
| Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA | |
| 02110-1301, USA. */ | |
| /* We might put this in a library someday so it could be dynamically | |
| loaded, but for now it's not necessary. */ | |
| #include "sysdep.h" | |
| #include "bfd.h" | |
| #include "libiberty.h" | |
| #include "filenames.h" | |
| #include <time.h> | |
| #include <assert.h> | |
| #include "bucomm.h" | |
| /* Error reporting. */ | |
| char *program_name; | |
| void | |
| bfd_nonfatal (const char *string) | |
| { | |
| const char *errmsg; | |
| enum bfd_error err = bfd_get_error (); | |
| if (err == bfd_error_no_error) | |
| errmsg = _("cause of error unknown"); | |
| else | |
| errmsg = bfd_errmsg (err); | |
| fflush (stdout); | |
| if (string) | |
| fprintf (stderr, "%s: %s: %s\n", program_name, string, errmsg); | |
| else | |
| fprintf (stderr, "%s: %s\n", program_name, errmsg); | |
| } | |
| /* Issue a non fatal error message. FILENAME, or if NULL then BFD, | |
| are used to indicate the problematic file. SECTION, if non NULL, | |
| is used to provide a section name. If FORMAT is non-null, then it | |
| is used to print additional information via vfprintf. Finally the | |
| bfd error message is printed. In summary, error messages are of | |
| one of the following forms: | |
| PROGRAM: file: bfd-error-message | |
| PROGRAM: file[section]: bfd-error-message | |
| PROGRAM: file: printf-message: bfd-error-message | |
| PROGRAM: file[section]: printf-message: bfd-error-message. */ | |
| void | |
| bfd_nonfatal_message (const char *filename, | |
| const bfd *abfd, | |
| const asection *section, | |
| const char *format, ...) | |
| { | |
| const char *errmsg; | |
| const char *section_name; | |
| enum bfd_error err = bfd_get_error (); | |
| if (err == bfd_error_no_error) | |
| errmsg = _("cause of error unknown"); | |
| else | |
| errmsg = bfd_errmsg (err); | |
| fflush (stdout); | |
| section_name = NULL; | |
| fprintf (stderr, "%s", program_name); | |
| if (abfd) | |
| { | |
| if (!filename) | |
| filename = bfd_get_archive_filename (abfd); | |
| if (section) | |
| section_name = bfd_section_name (section); | |
| } | |
| if (section_name) | |
| fprintf (stderr, ": %s[%s]", filename, section_name); | |
| else | |
| fprintf (stderr, ": %s", filename); | |
| if (format) | |
| { | |
| va_list args; | |
| va_start (args, format); | |
| fprintf (stderr, ": "); | |
| vfprintf (stderr, format, args); | |
| va_end (args); | |
| } | |
| fprintf (stderr, ": %s\n", errmsg); | |
| } | |
| void | |
| bfd_fatal (const char *string) | |
| { | |
| bfd_nonfatal (string); | |
| xexit (1); | |
| } | |
| void | |
| report (const char * format, va_list args) | |
| { | |
| fflush (stdout); | |
| fprintf (stderr, "%s: ", program_name); | |
| vfprintf (stderr, format, args); | |
| putc ('\n', stderr); | |
| } | |
| void | |
| fatal (const char *format, ...) | |
| { | |
| va_list args; | |
| va_start (args, format); | |
| report (format, args); | |
| va_end (args); | |
| xexit (1); | |
| } | |
| void | |
| non_fatal (const char *format, ...) | |
| { | |
| va_list args; | |
| va_start (args, format); | |
| report (format, args); | |
| va_end (args); | |
| } | |
| /* Like xmalloc except that ABFD's objalloc memory is returned. | |
| Use objalloc_free_block to free this memory and all more recently | |
| allocated, or more usually, leave it to bfd_close to free. */ | |
| void * | |
| bfd_xalloc (bfd *abfd, size_t size) | |
| { | |
| void *ret = bfd_alloc (abfd, size); | |
| if (ret == NULL) | |
| bfd_fatal (NULL); | |
| return ret; | |
| } | |
| /* Set the default BFD target based on the configured target. Doing | |
| this permits the binutils to be configured for a particular target, | |
| and linked against a shared BFD library which was configured for a | |
| different target. */ | |
| void | |
| set_default_bfd_target (void) | |
| { | |
| /* The macro TARGET is defined by Makefile. */ | |
| const char *target = TARGET; | |
| if (! bfd_set_default_target (target)) | |
| fatal (_("can't set BFD default target to `%s': %s"), | |
| target, bfd_errmsg (bfd_get_error ())); | |
| } | |
| /* After a FALSE return from bfd_check_format_matches with | |
| bfd_get_error () == bfd_error_file_ambiguously_recognized, print | |
| the possible matching targets and free the list of targets. */ | |
| void | |
| list_matching_formats (char **matching) | |
| { | |
| fflush (stdout); | |
| fprintf (stderr, _("%s: Matching formats:"), program_name); | |
| char **p = matching; | |
| while (*p) | |
| fprintf (stderr, " %s", *p++); | |
| free (matching); | |
| fputc ('\n', stderr); | |
| } | |
| /* List the supported targets. */ | |
| void | |
| list_supported_targets (const char *name, FILE *f) | |
| { | |
| int t; | |
| const char **targ_names; | |
| if (name == NULL) | |
| fprintf (f, _("Supported targets:")); | |
| else | |
| fprintf (f, _("%s: supported targets:"), name); | |
| targ_names = bfd_target_list (); | |
| for (t = 0; targ_names[t] != NULL; t++) | |
| fprintf (f, " %s", targ_names[t]); | |
| fprintf (f, "\n"); | |
| free (targ_names); | |
| } | |
| /* List the supported architectures. */ | |
| void | |
| list_supported_architectures (const char *name, FILE *f) | |
| { | |
| const char ** arch; | |
| const char ** arches; | |
| if (name == NULL) | |
| fprintf (f, _("Supported architectures:")); | |
| else | |
| fprintf (f, _("%s: supported architectures:"), name); | |
| for (arch = arches = bfd_arch_list (); *arch; arch++) | |
| fprintf (f, " %s", *arch); | |
| fprintf (f, "\n"); | |
| free (arches); | |
| } | |
| static const char * | |
| endian_string (enum bfd_endian endian) | |
| { | |
| switch (endian) | |
| { | |
| case BFD_ENDIAN_BIG: return _("big endian"); | |
| case BFD_ENDIAN_LITTLE: return _("little endian"); | |
| default: return _("endianness unknown"); | |
| } | |
| } | |
| /* Data passed to do_display_target and other target iterators. */ | |
| struct display_target { | |
| /* Temp file. */ | |
| char *filename; | |
| /* Return status. */ | |
| int error; | |
| /* Number of targets. */ | |
| int count; | |
| /* Size of info in bytes. */ | |
| size_t alloc; | |
| /* Per-target info. */ | |
| struct { | |
| /* Target name. */ | |
| const char *name; | |
| /* Non-zero if target/arch combination supported. */ | |
| unsigned char arch[bfd_arch_last - bfd_arch_obscure - 1]; | |
| } *info; | |
| }; | |
| /* List the targets that BFD is configured to support, each followed | |
| by its endianness and the architectures it supports. Also build | |
| info about target/archs. */ | |
| static int | |
| do_display_target (const bfd_target *targ, void *data) | |
| { | |
| struct display_target *param = (struct display_target *) data; | |
| bfd *abfd; | |
| size_t amt; | |
| param->count += 1; | |
| amt = param->count * sizeof (*param->info); | |
| if (param->alloc < amt) | |
| { | |
| size_t size = ((param->count < 64 ? 64 : param->count) | |
| * sizeof (*param->info) * 2); | |
| param->info = xrealloc (param->info, size); | |
| memset ((char *) param->info + param->alloc, 0, size - param->alloc); | |
| param->alloc = size; | |
| } | |
| param->info[param->count - 1].name = targ->name; | |
| printf (_("%s\n (header %s, data %s)\n"), targ->name, | |
| endian_string (targ->header_byteorder), | |
| endian_string (targ->byteorder)); | |
| abfd = bfd_openw (param->filename, targ->name); | |
| if (abfd == NULL) | |
| { | |
| bfd_nonfatal (param->filename); | |
| param->error = 1; | |
| } | |
| else if (!bfd_set_format (abfd, bfd_object)) | |
| { | |
| if (bfd_get_error () != bfd_error_invalid_operation) | |
| { | |
| bfd_nonfatal (targ->name); | |
| param->error = 1; | |
| } | |
| } | |
| else | |
| { | |
| enum bfd_architecture a; | |
| for (a = bfd_arch_obscure + 1; a < bfd_arch_last; a++) | |
| if (bfd_set_arch_mach (abfd, a, 0)) | |
| { | |
| printf (" %s\n", bfd_printable_arch_mach (a, 0)); | |
| param->info[param->count - 1].arch[a - bfd_arch_obscure - 1] = 1; | |
| } | |
| } | |
| if (abfd != NULL) | |
| bfd_close_all_done (abfd); | |
| return param->error; | |
| } | |
| static void | |
| display_target_list (struct display_target *arg) | |
| { | |
| arg->filename = make_temp_file (NULL); | |
| arg->error = 0; | |
| arg->count = 0; | |
| arg->alloc = 0; | |
| arg->info = NULL; | |
| bfd_iterate_over_targets (do_display_target, arg); | |
| unlink (arg->filename); | |
| free (arg->filename); | |
| } | |
| /* Calculate how many targets we can print across the page. */ | |
| static int | |
| do_info_size (int targ, int width, const struct display_target *arg) | |
| { | |
| while (targ < arg->count) | |
| { | |
| width -= strlen (arg->info[targ].name) + 1; | |
| if (width < 0) | |
| return targ; | |
| ++targ; | |
| } | |
| return targ; | |
| } | |
| /* Print header of target names. */ | |
| static void | |
| do_info_header (int targ, int stop_targ, const struct display_target *arg) | |
| { | |
| while (targ != stop_targ) | |
| printf ("%s ", arg->info[targ++].name); | |
| } | |
| /* Print a table row. */ | |
| static void | |
| do_info_row (int targ, int stop_targ, enum bfd_architecture a, | |
| const struct display_target *arg) | |
| { | |
| while (targ != stop_targ) | |
| { | |
| if (arg->info[targ].arch[a - bfd_arch_obscure - 1]) | |
| fputs (arg->info[targ].name, stdout); | |
| else | |
| { | |
| int l = strlen (arg->info[targ].name); | |
| while (l--) | |
| putchar ('-'); | |
| } | |
| ++targ; | |
| if (targ != stop_targ) | |
| putchar (' '); | |
| } | |
| } | |
| /* Print tables of all the target-architecture combinations that | |
| BFD has been configured to support. */ | |
| static void | |
| display_target_tables (const struct display_target *arg) | |
| { | |
| const char *columns; | |
| int width, start_targ, stop_targ; | |
| enum bfd_architecture arch; | |
| int longest_arch = 0; | |
| for (arch = bfd_arch_obscure + 1; arch < bfd_arch_last; arch++) | |
| { | |
| const char *s = bfd_printable_arch_mach (arch, 0); | |
| int len = strlen (s); | |
| if (len > longest_arch) | |
| longest_arch = len; | |
| } | |
| width = 0; | |
| columns = getenv ("COLUMNS"); | |
| if (columns != NULL) | |
| width = atoi (columns); | |
| if (width == 0) | |
| width = 80; | |
| for (start_targ = 0; start_targ < arg->count; start_targ = stop_targ) | |
| { | |
| stop_targ = do_info_size (start_targ, width - longest_arch - 1, arg); | |
| printf ("\n%*s", longest_arch + 1, " "); | |
| do_info_header (start_targ, stop_targ, arg); | |
| putchar ('\n'); | |
| for (arch = bfd_arch_obscure + 1; arch < bfd_arch_last; arch++) | |
| { | |
| if (strcmp (bfd_printable_arch_mach (arch, 0), "UNKNOWN!") != 0) | |
| { | |
| printf ("%*s ", longest_arch, | |
| bfd_printable_arch_mach (arch, 0)); | |
| do_info_row (start_targ, stop_targ, arch, arg); | |
| putchar ('\n'); | |
| } | |
| } | |
| } | |
| } | |
| int | |
| display_info (void) | |
| { | |
| struct display_target arg; | |
| printf (_("BFD header file version %s\n"), BFD_VERSION_STRING); | |
| display_target_list (&arg); | |
| if (!arg.error) | |
| display_target_tables (&arg); | |
| return arg.error; | |
| } | |
| /* Display the archive header for an element as if it were an ls -l listing: | |
| Mode User\tGroup\tSize\tDate Name */ | |
| void | |
| print_arelt_descr (FILE *file, bfd *abfd, bool verbose, bool offsets) | |
| { | |
| struct stat buf; | |
| if (verbose) | |
| { | |
| if (bfd_stat_arch_elt (abfd, &buf) == 0) | |
| { | |
| char modebuf[11]; | |
| char timebuf[40]; | |
| time_t when = buf.st_mtime; | |
| const char *ctime_result = (const char *) ctime (&when); | |
| /* PR binutils/17605: Check for corrupt time values. */ | |
| if (ctime_result == NULL) | |
| sprintf (timebuf, _("<time data corrupt>")); | |
| else | |
| /* POSIX format: skip weekday and seconds from ctime output. */ | |
| sprintf (timebuf, "%.12s %.4s", ctime_result + 4, ctime_result + 20); | |
| mode_string (buf.st_mode, modebuf); | |
| modebuf[10] = '\0'; | |
| /* POSIX 1003.2/D11 says to skip first character (entry type). */ | |
| fprintf (file, "%s %ld/%ld %6" PRIu64 " %s ", modebuf + 1, | |
| (long) buf.st_uid, (long) buf.st_gid, | |
| (uint64_t) buf.st_size, timebuf); | |
| } | |
| } | |
| fprintf (file, "%s", bfd_get_filename (abfd)); | |
| if (offsets) | |
| { | |
| if (bfd_is_thin_archive (abfd) && abfd->proxy_origin) | |
| fprintf (file, " 0x%lx", (unsigned long) abfd->proxy_origin); | |
| else if (!bfd_is_thin_archive (abfd) && abfd->origin) | |
| fprintf (file, " 0x%lx", (unsigned long) abfd->origin); | |
| } | |
| fprintf (file, "\n"); | |
| } | |
| /* Return a path for a new temporary file in the same directory | |
| as file PATH. */ | |
| static char * | |
| template_in_dir (const char *path) | |
| { | |
| #define template "stXXXXXX" | |
| const char *slash = strrchr (path, '/'); | |
| char *tmpname; | |
| size_t len; | |
| #ifdef HAVE_DOS_BASED_FILE_SYSTEM | |
| { | |
| /* We could have foo/bar\\baz, or foo\\bar, or d:bar. */ | |
| char *bslash = strrchr (path, '\\'); | |
| if (slash == NULL || (bslash != NULL && bslash > slash)) | |
| slash = bslash; | |
| if (slash == NULL && path[0] != '\0' && path[1] == ':') | |
| slash = path + 1; | |
| } | |
| #endif | |
| if (slash != (char *) NULL) | |
| { | |
| len = slash - path; | |
| tmpname = (char *) xmalloc (len + sizeof (template) + 2); | |
| memcpy (tmpname, path, len); | |
| #ifdef HAVE_DOS_BASED_FILE_SYSTEM | |
| /* If tmpname is "X:", appending a slash will make it a root | |
| directory on drive X, which is NOT the same as the current | |
| directory on drive X. */ | |
| if (len == 2 && tmpname[1] == ':') | |
| tmpname[len++] = '.'; | |
| #endif | |
| tmpname[len++] = '/'; | |
| } | |
| else | |
| { | |
| tmpname = (char *) xmalloc (sizeof (template)); | |
| len = 0; | |
| } | |
| memcpy (tmpname + len, template, sizeof (template)); | |
| return tmpname; | |
| #undef template | |
| } | |
| /* Return the name of a created temporary file in the same directory | |
| as FILENAME. */ | |
| char * | |
| make_tempname (const char *filename, int *ofd) | |
| { | |
| char *tmpname = template_in_dir (filename); | |
| int fd; | |
| #ifdef HAVE_MKSTEMP | |
| fd = mkstemp (tmpname); | |
| #else | |
| tmpname = mktemp (tmpname); | |
| if (tmpname == NULL) | |
| fd = -1; | |
| else | |
| fd = open (tmpname, O_RDWR | O_CREAT | O_EXCL, 0600); | |
| #endif | |
| if (fd == -1) | |
| { | |
| free (tmpname); | |
| bfd_set_error (bfd_error_system_call); | |
| return NULL; | |
| } | |
| *ofd = fd; | |
| return tmpname; | |
| } | |
| /* Return the name of a created temporary directory inside the | |
| directory containing FILENAME. */ | |
| char * | |
| make_tempdir (const char *filename) | |
| { | |
| char *tmpname = template_in_dir (filename); | |
| char *ret; | |
| #ifdef HAVE_MKDTEMP | |
| ret = mkdtemp (tmpname); | |
| #else | |
| ret = mktemp (tmpname); | |
| #if defined (_WIN32) && !defined (__CYGWIN32__) | |
| if (mkdir (tmpname) != 0) | |
| ret = NULL; | |
| #else | |
| if (mkdir (tmpname, 0700) != 0) | |
| ret = NULL; | |
| #endif | |
| #endif | |
| if (ret == NULL) | |
| { | |
| free (tmpname); | |
| bfd_set_error (bfd_error_system_call); | |
| } | |
| return ret; | |
| } | |
| /* Parse a string into a VMA, with a fatal error if it can't be | |
| parsed. */ | |
| bfd_vma | |
| parse_vma (const char *s, const char *arg) | |
| { | |
| bfd_vma ret; | |
| const char *end; | |
| ret = bfd_scan_vma (s, &end, 0); | |
| if (*end != '\0') | |
| fatal (_("%s: bad number: %s"), arg, s); | |
| return ret; | |
| } | |
| /* Returns the size of the named file. If the file does not | |
| exist, or if it is not a real file, then a suitable non-fatal | |
| error message is printed and (off_t) -1 is returned. */ | |
| off_t | |
| get_file_size (const char * file_name) | |
| { | |
| struct stat statbuf; | |
| if (file_name == NULL) | |
| return (off_t) -1; | |
| if (stat (file_name, &statbuf) < 0) | |
| { | |
| if (errno == ENOENT) | |
| non_fatal (_("'%s': No such file"), file_name); | |
| else | |
| non_fatal (_("Warning: could not locate '%s'. reason: %s"), | |
| file_name, strerror (errno)); | |
| } | |
| else if (S_ISDIR (statbuf.st_mode)) | |
| non_fatal (_("Warning: '%s' is a directory"), file_name); | |
| else if (! S_ISREG (statbuf.st_mode)) | |
| non_fatal (_("Warning: '%s' is not an ordinary file"), file_name); | |
| else if (statbuf.st_size < 0) | |
| non_fatal (_("Warning: '%s' has negative size, probably it is too large"), | |
| file_name); | |
| #if defined (_WIN32) && !defined (__CYGWIN__) | |
| else if (statbuf.st_size == 0) | |
| { | |
| /* MS-Windows 'stat' reports the null device as a regular file; | |
| fix that. */ | |
| int fd = open (file_name, O_RDONLY | O_BINARY); | |
| if (isatty (fd)) | |
| { | |
| close (fd); | |
| non_fatal (_("Warning: '%s' is not an ordinary file"), | |
| /* libtool wants to see /dev/null in the output. */ | |
| strcasecmp (file_name, "nul") ? file_name : "/dev/null"); | |
| } | |
| } | |
| #endif | |
| else | |
| return statbuf.st_size; | |
| return (off_t) -1; | |
| } | |
| /* Return the filename in a static buffer. */ | |
| const char * | |
| bfd_get_archive_filename (const bfd *abfd) | |
| { | |
| static size_t curr = 0; | |
| static char *buf; | |
| size_t needed; | |
| assert (abfd != NULL); | |
| if (abfd->my_archive == NULL | |
| || bfd_is_thin_archive (abfd->my_archive)) | |
| return bfd_get_filename (abfd); | |
| needed = (strlen (bfd_get_filename (abfd->my_archive)) | |
| + strlen (bfd_get_filename (abfd)) + 3); | |
| if (needed > curr) | |
| { | |
| if (curr) | |
| free (buf); | |
| curr = needed + (needed >> 1); | |
| buf = (char *) xmalloc (curr); | |
| } | |
| sprintf (buf, "%s(%s)", bfd_get_filename (abfd->my_archive), | |
| bfd_get_filename (abfd)); | |
| return buf; | |
| } | |
| /* Returns TRUE iff PATHNAME, a filename of an archive member, | |
| is valid for writing. For security reasons absolute paths | |
| and paths containing /../ are not allowed. See PR 17533. */ | |
| bool | |
| is_valid_archive_path (char const * pathname) | |
| { | |
| const char * n = pathname; | |
| if (IS_ABSOLUTE_PATH (n)) | |
| return false; | |
| while (*n) | |
| { | |
| if (*n == '.' && *++n == '.' && ( ! *++n || IS_DIR_SEPARATOR (*n))) | |
| return false; | |
| while (*n && ! IS_DIR_SEPARATOR (*n)) | |
| n++; | |
| while (IS_DIR_SEPARATOR (*n)) | |
| n++; | |
| } | |
| return true; | |
| } |