binutils-gdb/libctf/ctf-subr.c
Nick Alcock 926c9e7665 libctf, binutils, include, ld: gettextize and improve error handling
This commit follows on from the earlier commit "libctf, ld, binutils:
add textual error/warning reporting for libctf" and converts every error
in libctf that was reported using ctf_dprintf to use ctf_err_warn
instead, gettextizing them in the process, using N_() where necessary to
avoid doing gettext calls unless an error message is actually generated,
and rephrasing some error messages for ease of translation.

This requires a slight change in the ctf_errwarning_next API: this API
is public but has not been in a release yet, so can still change freely.
The problem is that many errors are emitted at open time (whether
opening of a CTF dict, or opening of a CTF archive): the former of these
throws away its incompletely-initialized ctf_file_t rather than return
it, and the latter has no ctf_file_t at all. So errors and warnings
emitted at open time cannot be stored in the ctf_file_t, and have to go
elsewhere.

We put them in a static local in ctf-subr.c (which is not very
thread-safe: a later commit will improve things here): ctf_err_warn with
a NULL fp adds to this list, and the public interface
ctf_errwarning_next with a NULL fp retrieves from it.

We need a slight exception from the usual iterator rules in this case:
with a NULL fp, there is nowhere to store the ECTF_NEXT_END "error"
which signifies the end of iteration, so we add a new err parameter to
ctf_errwarning_next which is used to report such iteration-related
errors.  (If an fp is provided -- i.e., if not reporting open errors --
this is optional, but even if it's optional it's still an API change.
This is actually useful from a usability POV as well, since
ctf_errwarning_next is usually called when there's been an error, so
overwriting the error code with ECTF_NEXT_END is not very helpful!
So, unusually, ctf_errwarning_next now uses the passed fp for its
error code *only* if no errp pointer is passed in, and leaves it
untouched otherwise.)

ld, objdump and readelf are adapted to call ctf_errwarning_next with a
NULL fp to report open errors where appropriate.

The ctf_err_warn API also has to change, gaining a new error-number
parameter which is used to add the error message corresponding to that
error number into the debug stream when LIBCTF_DEBUG is enabled:
changing this API is easy at this point since we are already touching
all existing calls to gettextize them.  We need this because the debug
stream should contain the errno's message, but the error reported in the
error/warning stream should *not*, because the caller will probably
report it themselves at failure time regardless, and reporting it in
every error message that leads up to it leads to a ridiculous chattering
on failure, which is likely to end up as ridiculous chattering on stderr
(trimmed a bit):

CTF error: `ld/testsuite/ld-ctf/A.c (0): lookup failure for type 3: flags 1: The parent CTF dictionary is unavailable'
CTF error: `ld/testsuite/ld-ctf/A.c (0): struct/union member type hashing error during type hashing for type 80000001, kind 6: The parent CTF dictionary is unavailable'
CTF error: `deduplicating link variable emission failed for ld/testsuite/ld-ctf/A.c: The parent CTF dictionary is unavailable'
ld/.libs/lt-ld-new: warning: CTF linking failed; output will have no CTF section: `The parent CTF dictionary is unavailable'

We only need to be told that the parent CTF dictionary is unavailable
*once*, not over and over again!

errmsgs are still emitted on warning generation, because warnings do not
usually lead to a failure propagated up to the caller and reported
there.

Debug-stream messages are not translated.  If translation is turned on,
there will be a mixture of English and translated messages in the debug
stream, but rather that than burden the translators with debug-only
output.

binutils/ChangeLog
2020-08-27  Nick Alcock  <nick.alcock@oracle.com>

	* objdump.c (dump_ctf_archive_member): Move error-
	reporting...
	(dump_ctf_errs): ... into this separate function.
	(dump_ctf): Call it on open errors.
	* readelf.c (dump_ctf_archive_member): Move error-
	reporting...
	(dump_ctf_errs): ... into this separate function.  Support
	calls with NULL fp. Adjust for new err parameter to
	ctf_errwarning_next.
	(dump_section_as_ctf): Call it on open errors.

include/ChangeLog
2020-08-27  Nick Alcock  <nick.alcock@oracle.com>

	* ctf-api.h (ctf_errwarning_next): New err parameter.

ld/ChangeLog
2020-08-27  Nick Alcock  <nick.alcock@oracle.com>

	* ldlang.c (lang_ctf_errs_warnings): Support calls with NULL fp.
	Adjust for new err parameter to ctf_errwarning_next.  Only
	check for assertion failures when fp is non-NULL.
	(ldlang_open_ctf): Call it on open errors.
	* testsuite/ld-ctf/ctf.exp: Always use the C locale to avoid
	breaking the diags tests.

libctf/ChangeLog
2020-08-27  Nick Alcock  <nick.alcock@oracle.com>

	* ctf-subr.c (open_errors): New list.
	(ctf_err_warn): Calls with NULL fp append to open_errors.  Add err
	parameter, and use it to decorate the debug stream with errmsgs.
	(ctf_err_warn_to_open): Splice errors from a CTF dict into the
	open_errors.
	(ctf_errwarning_next): Calls with NULL fp report from open_errors.
	New err param to report iteration errors (including end-of-iteration)
	when fp is NULL.
	(ctf_assert_fail_internal): Adjust ctf_err_warn call for new err
	parameter: gettextize.
	* ctf-impl.h (ctfo_get_vbytes): Add ctf_file_t parameter.
	(LCTF_VBYTES): Adjust.
	(ctf_err_warn_to_open): New.
	(ctf_err_warn): Adjust.
	(ctf_bundle): Used in only one place: move...
	* ctf-create.c: ... here.
	(enumcmp): Use ctf_err_warn, not ctf_dprintf, passing the err number
	down as needed.  Don't emit the errmsg.  Gettextize.
	(membcmp): Likewise.
	(ctf_add_type_internal): Likewise.
	(ctf_write_mem): Likewise.
	(ctf_compress_write): Likewise.  Report errors writing the header or
	body.
	(ctf_write): Likewise.
	* ctf-archive.c (ctf_arc_write_fd): Use ctf_err_warn, not
	ctf_dprintf, and gettextize, as above.
	(ctf_arc_write): Likewise.
	(ctf_arc_bufopen): Likewise.
	(ctf_arc_open_internal): Likewise.
	* ctf-labels.c (ctf_label_iter): Likewise.
	* ctf-open-bfd.c (ctf_bfdclose): Likewise.
	(ctf_bfdopen): Likewise.
	(ctf_bfdopen_ctfsect): Likewise.
	(ctf_fdopen): Likewise.
	* ctf-string.c (ctf_str_write_strtab): Likewise.
	* ctf-types.c (ctf_type_resolve): Likewise.
	* ctf-open.c (get_vbytes_common): Likewise. Pass down the ctf dict.
	(get_vbytes_v1): Pass down the ctf dict.
	(get_vbytes_v2): Likewise.
	(flip_ctf): Likewise.
	(flip_types): Likewise. Use ctf_err_warn, not ctf_dprintf, and
	gettextize, as above.
	(upgrade_types_v1): Adjust calls.
	(init_types): Use ctf_err_warn, not ctf_dprintf, as above.
	(ctf_bufopen_internal): Likewise. Adjust calls. Transplant errors
	emitted into individual dicts into the open errors if this turns
	out to be a failed open in the end.
	* ctf-dump.c (ctf_dump_format_type): Adjust ctf_err_warn for new err
	argument.  Gettextize.  Don't emit the errmsg.
	(ctf_dump_funcs): Likewise.  Collapse err label into its only case.
	(ctf_dump_type): Likewise.
	* ctf-link.c (ctf_create_per_cu): Adjust ctf_err_warn for new err
	argument.  Gettextize.  Don't emit the errmsg.
	(ctf_link_one_type): Likewise.
	(ctf_link_lazy_open): Likewise.
	(ctf_link_one_input_archive): Likewise.
	(ctf_link_deduplicating_count_inputs): Likewise.
	(ctf_link_deduplicating_open_inputs): Likewise.
	(ctf_link_deduplicating_close_inputs): Likewise.
	(ctf_link_deduplicating): Likewise.
	(ctf_link): Likewise.
	(ctf_link_deduplicating_per_cu): Likewise. Add some missed
	ctf_set_errnos to obscure error cases.
	* ctf-dedup.c (ctf_dedup_rhash_type): Adjust ctf_err_warn for new
	err argument.  Gettextize.  Don't emit the errmsg.
	(ctf_dedup_populate_mappings): Likewise.
	(ctf_dedup_detect_name_ambiguity): Likewise.
	(ctf_dedup_init): Likewise.
	(ctf_dedup_multiple_input_dicts): Likewise.
	(ctf_dedup_conflictify_unshared): Likewise.
	(ctf_dedup): Likewise.
	(ctf_dedup_rwalk_one_output_mapping): Likewise.
	(ctf_dedup_id_to_target): Likewise.
	(ctf_dedup_emit_type): Likewise.
	(ctf_dedup_emit_struct_members): Likewise.
	(ctf_dedup_populate_type_mapping): Likewise.
	(ctf_dedup_populate_type_mappings): Likewise.
	(ctf_dedup_emit): Likewise.
	(ctf_dedup_hash_type): Likewise. Fix a bit of messed-up error
	status setting.
	(ctf_dedup_rwalk_one_output_mapping): Likewise. Don't hide
	unknown-type-kind messages (which signify file corruption).
2020-08-27 13:15:43 +01:00

345 lines
8.2 KiB
C

/* Simple subrs.
Copyright (C) 2019-2020 Free Software Foundation, Inc.
This file is part of libctf.
libctf 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, 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; see the file COPYING. If not see
<http://www.gnu.org/licenses/>. */
#include <ctf-impl.h>
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
#include <sys/types.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#ifndef ENOTSUP
#define ENOTSUP ENOSYS
#endif
int _libctf_version = CTF_VERSION; /* Library client version. */
int _libctf_debug = 0; /* Debugging messages enabled. */
/* Private, read-only mmap from a file, with fallback to copying.
No handling of page-offset issues at all: the caller must allow for that. */
_libctf_malloc_ void *
ctf_mmap (size_t length, size_t offset, int fd)
{
void *data;
#ifdef HAVE_MMAP
data = mmap (NULL, length, PROT_READ, MAP_PRIVATE, fd, offset);
if (data == MAP_FAILED)
data = NULL;
#else
if ((data = malloc (length)) != NULL)
{
if (ctf_pread (fd, data, length, offset) <= 0)
{
free (data);
data = NULL;
}
}
#endif
return data;
}
void
ctf_munmap (void *buf, size_t length _libctf_unused_)
{
#ifdef HAVE_MMAP
(void) munmap (buf, length);
#else
free (buf);
#endif
}
ssize_t
ctf_pread (int fd, void *buf, ssize_t count, off_t offset)
{
ssize_t len;
size_t acc = 0;
char *data = (char *) buf;
#ifdef HAVE_PREAD
while (count > 0)
{
errno = 0;
if (((len = pread (fd, data, count, offset)) < 0) &&
errno != EINTR)
return len;
if (errno == EINTR)
continue;
acc += len;
if (len == 0) /* EOF. */
return acc;
count -= len;
offset += len;
data += len;
}
return acc;
#else
off_t orig_off;
if ((orig_off = lseek (fd, 0, SEEK_CUR)) < 0)
return -1;
if ((lseek (fd, offset, SEEK_SET)) < 0)
return -1;
while (count > 0)
{
errno = 0;
if (((len = read (fd, data, count)) < 0) &&
errno != EINTR)
return len;
if (errno == EINTR)
continue;
acc += len;
if (len == 0) /* EOF. */
break;
count -= len;
data += len;
}
if ((lseek (fd, orig_off, SEEK_SET)) < 0)
return -1; /* offset is smashed. */
#endif
return acc;
}
/* Set the CTF library client version to the specified version. If version is
zero, we just return the default library version number. */
int
ctf_version (int version)
{
if (version < 0)
{
errno = EINVAL;
return -1;
}
if (version > 0)
{
/* Dynamic version switching is not presently supported. */
if (version != CTF_VERSION)
{
errno = ENOTSUP;
return -1;
}
ctf_dprintf ("ctf_version: client using version %d\n", version);
_libctf_version = version;
}
return _libctf_version;
}
void
libctf_init_debug (void)
{
static int inited;
if (!inited)
{
_libctf_debug = getenv ("LIBCTF_DEBUG") != NULL;
inited = 1;
}
}
void ctf_setdebug (int debug)
{
/* Ensure that libctf_init_debug() has been called, so that we don't get our
debugging-on-or-off smashed by the next call. */
libctf_init_debug();
_libctf_debug = debug;
ctf_dprintf ("CTF debugging set to %i\n", debug);
}
int ctf_getdebug (void)
{
return _libctf_debug;
}
_libctf_printflike_ (1, 2)
void ctf_dprintf (const char *format, ...)
{
if (_libctf_unlikely_ (_libctf_debug))
{
va_list alist;
va_start (alist, format);
fflush (stdout);
(void) fputs ("libctf DEBUG: ", stderr);
(void) vfprintf (stderr, format, alist);
va_end (alist);
}
}
/* This needs more attention to thread-safety later on. */
static ctf_list_t open_errors;
/* Errors and warnings. Report the warning or error to the list in FP (or the
open errors list if NULL): if ERR is nonzero it is the errno to report to the
debug stream instead of that recorded on fp. */
_libctf_printflike_ (4, 5)
extern void
ctf_err_warn (ctf_file_t *fp, int is_warning, int err,
const char *format, ...)
{
va_list alist;
ctf_err_warning_t *cew;
/* Don't bother reporting errors here: we can't do much about them if they
happen. If we're so short of memory that a tiny malloc doesn't work, a
vfprintf isn't going to work either and the caller will have to rely on the
ENOMEM return they'll be getting in short order anyway. */
if ((cew = malloc (sizeof (ctf_err_warning_t))) == NULL)
return;
cew->cew_is_warning = is_warning;
va_start (alist, format);
if (vasprintf (&cew->cew_text, format, alist) < 0)
{
free (cew);
va_end (alist);
return;
}
va_end (alist);
/* Include the error code only if there is one, and if this is not a warning.
(Warnings may not have a meaningful error code, since the warning may not
lead to unwinding up to the user.) */
if (!is_warning && (err != 0 || (fp && ctf_errno (fp) != 0)))
ctf_dprintf ("%s: %s (%s)\n", is_warning ? _("error") : _("warning"),
cew->cew_text, err != 0 ? ctf_errmsg (err)
: ctf_errmsg (ctf_errno (fp)));
else
ctf_dprintf ("%s: %s\n", is_warning ? _("error") : _("warning"),
cew->cew_text);
if (fp != NULL)
ctf_list_append (&fp->ctf_errs_warnings, cew);
else
ctf_list_append (&open_errors, cew);
}
/* Move all the errors/warnings from an fp into the open_errors. */
void
ctf_err_warn_to_open (ctf_file_t *fp)
{
ctf_list_splice (&open_errors, &fp->ctf_errs_warnings);
}
/* Error-warning reporting: an 'iterator' that returns errors and warnings from
the error/warning list, in order of emission. Errors and warnings are popped
after return: the caller must free the returned error-text pointer.
An fp of NULL returns CTF-open-time errors from the open_errors variable
above.
The treatment of errors from this function itself is somewhat unusual: it
will often be called on an error path, so we don't want to overwrite the
ctf_errno unless we have no choice. So, like ctf_bufopen et al, this
function takes an errp pointer where errors are reported. The pointer is
optional: if not set, errors are reported via the fp (if non-NULL). Calls
with neither fp nor errp set are mildly problematic because there is no clear
way to report end-of-iteration: you just have to assume that a NULL return
means the end, and not an iterator error. */
char *
ctf_errwarning_next (ctf_file_t *fp, ctf_next_t **it, int *is_warning,
int *errp)
{
ctf_next_t *i = *it;
char *ret;
ctf_list_t *errlist;
ctf_err_warning_t *cew;
if (fp)
errlist = &fp->ctf_errs_warnings;
else
errlist = &open_errors;
if (!i)
{
if ((i = ctf_next_create ()) == NULL)
{
if (errp)
*errp = ENOMEM;
else if (fp)
ctf_set_errno (fp, ENOMEM);
return NULL;
}
i->cu.ctn_fp = fp;
i->ctn_iter_fun = (void (*) (void)) ctf_errwarning_next;
*it = i;
}
if ((void (*) (void)) ctf_errwarning_next != i->ctn_iter_fun)
{
if (errp)
*errp = ECTF_NEXT_WRONGFUN;
else if (fp)
ctf_set_errno (fp, ECTF_NEXT_WRONGFUN);
return NULL;
}
if (fp != i->cu.ctn_fp)
{
if (errp)
*errp = ECTF_NEXT_WRONGFP;
else if (fp)
ctf_set_errno (fp, ECTF_NEXT_WRONGFP);
return NULL;
}
cew = ctf_list_next (errlist);
if (!cew)
{
ctf_next_destroy (i);
*it = NULL;
if (errp)
*errp = ECTF_NEXT_END;
else if (fp)
ctf_set_errno (fp, ECTF_NEXT_END);
return NULL;
}
if (is_warning)
*is_warning = cew->cew_is_warning;
ret = cew->cew_text;
ctf_list_delete (errlist, cew);
free (cew);
return ret;
}
void
ctf_assert_fail_internal (ctf_file_t *fp, const char *file, size_t line,
const char *exprstr)
{
ctf_err_warn (fp, 0, ECTF_INTERNAL, _("%s: %lu: libctf assertion failed: %s"),
file, (long unsigned int) line, exprstr);
ctf_set_errno (fp, ECTF_INTERNAL);
}