locale: Fix localedef exit code [BZ #22292]

Message ID 28d4b48a-bfff-4a59-6fdb-9cbe4c0a2391@redhat.com
State Committed
Headers

Commit Message

Carlos O'Donell Oct. 13, 2017, 7:16 p.m. UTC
  On 10/13/2017 06:19 AM, Florian Weimer wrote:
> On 10/13/2017 10:24 AM, Carlos O'Donell wrote:
> 
>> To fix this situation I have adopted the following high-level
>> changes:
>> * All errors are counted distinctly.
>> * All warnings are counted distinctly.
>> * All informative messages are not counted.
>> * Increasing verbosity cannot generate*more*  errors, and
>>    it previously did for errors conditional on verbose,
>>    this is now fixed.
>> * Increasing verbosity*can*  generate*more*  warnings.
>> * Making the output quiet cannot generate*less*  errors,
> 
> “fewer errors“ (in case this makes it into the commit message.

Fixed.

See attached patch which is git format-patch with the message
commit ready.

> The general approach of the patch looks okay to me.  I expect that
> one day, we might use the support/ infrastructure, potentially
> compiled for the host, in these programs as well, but that's not a
> current priority.

I agree completely. I was wondering if I could get this into support/
more easily, and then link that *.o file into all of localedef,
iconv, and locale.

If I did move it into support I think I'd want to avoid the global
modification and usage of the various parameters, and instead include
a reference to them passed to the record functions. I'd want to clean
it up even more, but I'm not at my threshold for doing this kind of
cleanup outside of locale/programs/, but at least this way we're half
way there to cleaning this up.

> 
>> @@ -1602,10 +1600,10 @@ collate_finish (struct localedef_t *locale, const struct charmap_t *charmap)
>>             {
>>               if (runp->weights[i].w[j]->weights == NULL)
>>                 {
>> -            WITH_CUR_LOCALE (error_at_line (0, 0, runp->file,
>> -                            runp->line,
>> -                            _("symbol `%s' not defined"),
>> -                            runp->weights[i].w[j]->name));
>> +            record_error_at_line (0, 0, runp->file,
>> +                          runp->line,
>> +                          _("symbol `%s' not defined"),
>> +                          runp->weights[i].w[j]->name);
> 
> runp->line fits on the preceding line (similar occurrences below).

Fixed. For ld-collate.c I fixed 4 instances where the line fit better moving
the arguments up.

>> @@ -1830,7 +1830,7 @@ symbol `%s' has the same encoding as"), (*eptr)->name);
>>         /* This seems not to be enforced by recent standards.  Don't
>>            emit an error, simply append UNDEFINED at the end.  */
>>         if (0)
>> -        WITH_CUR_LOCALE (error (0, 0, _("no definition of `UNDEFINED'")));
>> +        record_error (0, 0, _("no definition of `UNDEFINED'"));
> 
> Either turn this into a pure comment, or into a real warning.

Fixed. Nobody addes UNDEFINED at the end. We just add it ourselves in the tooling.
I tried enabling the error and almost all our locales error, so this can't be a
conservative thing to enable.

---

> 
>> diff --git a/locale/programs/record-status.h b/locale/programs/record-status.h
>> new file mode 100644
>> index 0000000..8701f8c
>> --- /dev/null
>> +++ b/locale/programs/record-status.h
>> @@ -0,0 +1,144 @@
>> +/* General definitions for recording error and warning status.
>> +   Copyright (C) 1998-2017 Free Software Foundation, Inc.
>> +   This file is part of the GNU C Library.
>> +
>> +   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; version 2 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, see<http://www.gnu.org/licenses/>.  */
>> +#ifndef _RECORD_STATUS_H
>> +#define _RECORD_STATUS_H 1
> 
> Missing empty line.

Fixed.

>> +/* The program using these functions must define these:  */
>> +extern int recorded_warning_count;
>> +extern int recorded_error_count;
>> +extern int be_quiet;
>> +extern int verbose;
> 
> You could turn these into tentative definitions (and update the
> comment).  Then the main program does not have to define them.

Fixed.
 
>> +/* Saved state of the current locale.  */
>> +struct locale_state
>> +{
>> +   const char *cur_locale;
>> +};
> 
> Drop the const for the change below.

OK.

>> +/* Alter the current locale to match the locale configured by the
>> +   user, and return the previous saved state.  */
>> +static inline struct locale_state
>> +push_locale (void)
>> +{
>> +  int saved_errno = errno;
>> +  const char *cl = setlocale (LC_CTYPE, NULL);
>> +  setlocale (LC_CTYPE, "");
>> +  errno = saved_errno;
>> +  return (struct locale_state) { .cur_locale = cl };
>> +}
>> +
>> +/* Use the saved state to restore the locale.  */
>> +static inline void
>> +pop_locale (struct locale_state ls)
>> +{
>> +  setlocale (LC_CTYPE, ls.cur_locale);
>> +}
> 
> The locale string returned by setlocale is only valid until the next
> setlocale call, I think, so you need to use strdup and free for the
> locale string.  strdup and setlocale need error checking.  Maybe
> introduce xsetlocale?

I don't want an xsetlocale, since the error is non-fatal to set the
locale for the error messages, you may be trying to use localedef
to recover from a corrupted or missing locale-archive.

See what I've done in v2.
- Used strdup
- Handled errors and printed messages via error, but don't
  abort the program.

>> +/* Wrapper to print verbose informative messages.  */
>> +static inline void
>> +record_verbose (FILE *stream, const char *format, ...)
>> +{
>> +  char *str;
>> +  va_list arg;
>> +
>> +  if (!verbose)
>> +    return;
>> +
>> +  if (!be_quiet)
>> +    {
>> +      struct locale_state ls;
>> +      va_start (arg, format);
>> +      ls = push_locale ();
>> +      vasprintf (&str, format, arg);
>> +      pop_locale (ls);
>> +      va_end (arg);
>> +      fprintf (stream, str);
>> +      free (str);
>> +    }
>> +}
> 
> These functions should not be inline and have printf/nonnull attributes.

Without inline the compiler will complain about unused functions. 
The point here is that these act like the macros they were replacing.
Unless you are suggesting we promote record-status.h to record-status.c
and do the work to link in a new object. At that point we should probably
just build a support object and link that in. I'd like to avoid this step
for today.

Added printf attribute for all functions.

Added nonull attribute for all functions.

> fprintf has a format string bug, should use fputs.  vasprintf needs error checking.

Fixed format string bug.

Switched to fputs.

Added error checking for all vasprintf (abort if -1 is returned).
 
>> +
>> +/* Wrapper to print warning messages.  We keep track of how
>> +   many were called because this effects our exit code.
>> +   Program must provide a definition of recorded_warning_count,
>> +   and be_quiet.  */
>> +static inline void
>> +record_warning (int status, int errnum, const char *format, ...)
>> +{
>> +  char *str;
>> +  va_list arg;
>> +  recorded_warning_count++;
>> +  if (!be_quiet)
>> +    {
>> +      struct locale_state ls;
>> +      va_start (arg, format);
>> +      ls = push_locale ();
>> +      vasprintf (&str, format, arg);
>> +      pop_locale (ls);
>> +      va_end (arg);
>> +      error (status, errnum, str);
>> +      free (str);
>> +    }
>> +}
> 
> The status/errnum parameters are unused and should be removed (status in particularly is conceptually unusable).

Agreed. Removed status/errnum and cleaned up all callers. Nothing set status/errnum to non-zero.

> error has a format string bug, should use error (status, errnum, "%s", str).

Fixed format string bug.

>> +/* Wrapper to print error messages.  We keep track of how
>> +   many were called because this effects our exit code.
>> +   Program must provide a definition of recorded_error_count
>> +   and be_quiet.  */
>> +static inline void
>> +record_error (int status, int errnum, const char *format, ...)
>> +{
>> +  char *str;
>> +  va_list arg;
>> +  recorded_error_count++;
>> +  if (!be_quiet)
>> +    {
>> +      struct locale_state ls;
>> +      va_start (arg, format);
>> +      ls = push_locale ();
>> +      vasprintf (&str, format, arg);
>> +      pop_locale (ls);
>> +      va_end (arg);
>> +      error (status, errnum, str);
>> +      free (str);
>> +    }
> 
> The condition needs to be !be_quiet || status != 0, otherwise be_quiet also disables fatal errors.  This applies to record_error_at_line, too.

Fixed this differently.

Even fatal errors should be quiet of --quiet is used, that's the purpose.

Added:

if (status != 0)
  exit (status)

v2 attached.

Question: Should locale/programs/locale.c 'static int verbose;' become 'int verbose'?
  

Comments

Florian Weimer Oct. 13, 2017, 7:24 p.m. UTC | #1
* Carlos O'Donell:

>> These functions should not be inline and have printf/nonnull attributes.
>
> Without inline the compiler will complain about unused functions.

You could add __attribute__ ((unused)).

>> The condition needs to be !be_quiet || status != 0, otherwise
>> be_quiet also disables fatal errors.  This applies to
>> record_error_at_line, too.
>
> Fixed this differently.
>
> Even fatal errors should be quiet of --quiet is used, that's the purpose.
>
> Added:
>
> if (status != 0)
>   exit (status)

The manual page says that fatal errors are still reported.  That's the
existing behavior, as far as I can see.

> Question: Should locale/programs/locale.c 'static int verbose;' become
> 'int verbose'?

What's the background of this question?
  
Carlos O'Donell Oct. 13, 2017, 7:43 p.m. UTC | #2
On 10/13/2017 12:24 PM, Florian Weimer wrote:
> * Carlos O'Donell:
> 
>>> These functions should not be inline and have printf/nonnull attributes.
>>
>> Without inline the compiler will complain about unused functions.
> 
> You could add __attribute__ ((unused)).

Fixed. I have no strong preference. Marked ((unused)).

>>> The condition needs to be !be_quiet || status != 0, otherwise
>>> be_quiet also disables fatal errors.  This applies to
>>> record_error_at_line, too.
>>
>> Fixed this differently.
>>
>> Even fatal errors should be quiet of --quiet is used, that's the purpose.
>>
>> Added:
>>
>> if (status != 0)
>>   exit (status)
> 
> The manual page says that fatal errors are still reported.  That's the
> existing behavior, as far as I can see.

Ah, I see your point.

OK, changed to report fatal errors.

>> Question: Should locale/programs/locale.c 'static int verbose;' become
>> 'int verbose'?
> 
> What's the background of this question?

The static file-scope verbose will have a different definition of verbose
from the expected verbose used by record-status.h. Thus a verbose enablement
in locale will not turn on verbose printing in record_verbose. There are no
such instances of this *today* in charmap.c and the other files used by locale
AFAICT. So it doesn't impact anything, but it should probably be 'int verbose'
in locale.c, but I'm not sure if I should change that or not, or leave it static
on purpose.

OK with the above changes *and* leaving locale.c as `static int verbose`?
  
Florian Weimer Oct. 13, 2017, 8:35 p.m. UTC | #3
* Carlos O'Donell:

> The static file-scope verbose will have a different definition of
> verbose from the expected verbose used by record-status.h. Thus a
> verbose enablement in locale will not turn on verbose printing in
> record_verbose. There are no such instances of this *today* in
> charmap.c and the other files used by locale AFAICT. So it doesn't
> impact anything, but it should probably be 'int verbose' in
> locale.c, but I'm not sure if I should change that or not, or leave
> it static on purpose.
>
> OK with the above changes *and* leaving locale.c as `static int
> verbose`?

Maybe at a comment on either definition of the verbose variable?

I think what you have now is a net improvement, so please go ahead and
commit it.
  
Carlos O'Donell Oct. 13, 2017, 9:41 p.m. UTC | #4
On 10/13/2017 01:35 PM, Florian Weimer wrote:
> * Carlos O'Donell:
> 
>> The static file-scope verbose will have a different definition of
>> verbose from the expected verbose used by record-status.h. Thus a
>> verbose enablement in locale will not turn on verbose printing in
>> record_verbose. There are no such instances of this *today* in
>> charmap.c and the other files used by locale AFAICT. So it doesn't
>> impact anything, but it should probably be 'int verbose' in
>> locale.c, but I'm not sure if I should change that or not, or leave
>> it static on purpose.
>>
>> OK with the above changes *and* leaving locale.c as `static int
>> verbose`?
> 
> Maybe at a comment on either definition of the verbose variable?

I added a comment to locale.c's definition of verbose and explained
what's going on.

i.e.

/* Nonzero if verbose output is wanted.  Note that this definition is
   file-local in scope, and does not extended to uses of verbose in
   record-status.h functions like record_verbose.  This means that this
   verbose will not enable record_verbose messages for uses from locale,
   but it does for uses from localdef (where verbose is global).  */
static int verbose;

I guess the text is not precisely accurate, since if you included
record-status.h directly, then it would work for those errors or
warnings you used in just locale.c, but the reader should get the
jist. I wasn't going to write 2 paragraphs about linkage.

> I think what you have now is a net improvement, so please go ahead and
> commit it.
 
Thanks. Pushed.
  
Carlos O'Donell Oct. 14, 2017, 5:48 a.m. UTC | #5
On 10/13/2017 02:41 PM, Carlos O'Donell wrote:
>> I think what you have now is a net improvement, so please go ahead and
>> commit it.
>  
> Thanks. Pushed.
 
This isn't quite the end of the story, and some temporary edits in my tree
hid 3 test failures and one quirk.

I have fixed all of them and pushed it as the fix for this bug.

The fixes are contained to:

localedata/tst-fmon.sh
localedata/tst-locale.sh

The basic problem is that all of the test locales are garbage, and now
that we have fixed the exit code, they all have warnings, and localedef
exits with 1, and the `set -e` kills the test. This is relatively easy
to fix and I included the cleanups for these tests.

The astute reader says "That's not 3!" Well, a failure in tst-locale.sh
fails to build a local later used by tst-digits, so that fixes itself
once you fix tst-locale.sh.

Similarly localedata/gen-locales.sh now accounts for the error
code of 1 from the SHIFT_JIS charmap which is not ISO C compliant and
raises a warning for that, which we now also properly detect.
  
Andreas Schwab Oct. 15, 2017, 9:14 a.m. UTC | #6
On Okt 13 2017, Carlos O'Donell <carlos@redhat.com> wrote:

> This has allowed the following fix:
> * Previously any warnings were being treated as errors
>   because they incremented error_message_count, but now
>   we properly return an exit status of 1 if there are
>   warnings but output was generated.

This breaks make localedata/install-locales.

Andreas.
  
Carlos O'Donell Oct. 15, 2017, 8:45 p.m. UTC | #7
On 10/15/2017 02:14 AM, Andreas Schwab wrote:
> On Okt 13 2017, Carlos O'Donell <carlos@redhat.com> wrote:
> 
>> This has allowed the following fix:
>> * Previously any warnings were being treated as errors
>>   because they incremented error_message_count, but now
>>   we properly return an exit status of 1 if there are
>>   warnings but output was generated.
> 
> This breaks make localedata/install-locales.

I'm looking into this.
  
Carlos O'Donell Oct. 15, 2017, 8:56 p.m. UTC | #8
On 10/15/2017 02:14 AM, Andreas Schwab wrote:
> On Okt 13 2017, Carlos O'Donell <carlos@redhat.com> wrote:
> 
>> This has allowed the following fix:
>> * Previously any warnings were being treated as errors
>>   because they incremented error_message_count, but now
>>   we properly return an exit status of 1 if there are
>>   warnings but output was generated.
> 
> This breaks make localedata/install-locales.

What breakage are you seeing?

I just retested this, thinking I'd missed something, but I get a clean
run...

[carlos@athas glibc-work]$ make localedata/install-locales install_root=/home/carlos/install/glibc-work/
make -r PARALLELMFLAGS="" -C /home/carlos/src/glibc-work objdir=`pwd` localedata/install-locales
make[1]: Entering directory '/mnt/ssd/carlos/src/glibc-work'
make  -C localedata install-locales
make[2]: Entering directory '/mnt/ssd/carlos/src/glibc-work/localedata'
.././scripts/mkinstalldirs /home/carlos/install/glibc-work//usr/lib64/locale
mkdir -p -- /home/carlos/install/glibc-work//usr/lib64/locale
aa_DJ.UTF-8... done
aa_DJ.ISO-8859-1... done
aa_ER.UTF-8... done
aa_ER.UTF-8@saaho... done
aa_ET.UTF-8... done
af_ZA.UTF-8... done
af_ZA.ISO-8859-1... done
agr_PE.UTF-8... done
ak_GH.UTF-8... done
am_ET.UTF-8... done
an_ES.UTF-8... done
an_ES.ISO-8859-15... done
anp_IN.UTF-8... done
ar_AE.UTF-8... done
ar_AE.ISO-8859-6... done
ar_BH.UTF-8... done
ar_BH.ISO-8859-6... done
ar_DZ.UTF-8... done
ar_DZ.ISO-8859-6... done
ar_EG.UTF-8... done
ar_EG.ISO-8859-6... done
ar_IN.UTF-8... done
ar_IQ.UTF-8... done
ar_IQ.ISO-8859-6... done
ar_JO.UTF-8... done
ar_JO.ISO-8859-6... done
ar_KW.UTF-8... done
ar_KW.ISO-8859-6... done
ar_LB.UTF-8... done
ar_LB.ISO-8859-6... done
ar_LY.UTF-8... done
ar_LY.ISO-8859-6... done
ar_MA.UTF-8... done
ar_MA.ISO-8859-6... done
ar_OM.UTF-8... done
ar_OM.ISO-8859-6... done
ar_QA.UTF-8... done
ar_QA.ISO-8859-6... done
ar_SA.UTF-8... done
ar_SA.ISO-8859-6... done
ar_SD.UTF-8... done
ar_SD.ISO-8859-6... done
ar_SS.UTF-8... done
ar_SY.UTF-8... done
ar_SY.ISO-8859-6... done
ar_TN.UTF-8... done
ar_TN.ISO-8859-6... done
ar_YE.UTF-8... done
ar_YE.ISO-8859-6... done
ayc_PE.UTF-8... done
az_AZ.UTF-8... done
az_IR.UTF-8... done
as_IN.UTF-8... done
ast_ES.UTF-8... done
ast_ES.ISO-8859-15... done
be_BY.UTF-8... done
be_BY.CP1251... done
be_BY.UTF-8@latin... done
bem_ZM.UTF-8... done
ber_DZ.UTF-8... done
ber_MA.UTF-8... done
bg_BG.UTF-8... done
bg_BG.CP1251... done
bhb_IN.UTF-8... done
bho_IN.UTF-8... done
bho_NP.UTF-8... done
bi_VU.UTF-8... done
bn_BD.UTF-8... done
bn_IN.UTF-8... done
bo_CN.UTF-8... done
bo_IN.UTF-8... done
br_FR.UTF-8... done
br_FR.ISO-8859-1... done
br_FR.ISO-8859-15@euro... done
brx_IN.UTF-8... done
bs_BA.UTF-8... done
bs_BA.ISO-8859-2... done
byn_ER.UTF-8... done
ca_AD.UTF-8... done
ca_AD.ISO-8859-15... done
ca_ES.UTF-8... done
ca_ES.ISO-8859-1... done
ca_ES.ISO-8859-15@euro... done
ca_FR.UTF-8... done
ca_FR.ISO-8859-15... done
ca_IT.UTF-8... done
ca_IT.ISO-8859-15... done
ce_RU.UTF-8... done
chr_US.UTF-8... done
cmn_TW.UTF-8... done
crh_UA.UTF-8... done
cs_CZ.UTF-8... done
cs_CZ.ISO-8859-2... done
csb_PL.UTF-8... done
cv_RU.UTF-8... done
cy_GB.UTF-8... done
cy_GB.ISO-8859-14... done
da_DK.UTF-8... done
da_DK.ISO-8859-1... done
de_AT.UTF-8... done
de_AT.ISO-8859-1... done
de_AT.ISO-8859-15@euro... done
de_BE.UTF-8... done
de_BE.ISO-8859-1... done
de_BE.ISO-8859-15@euro... done
de_CH.UTF-8... done
de_CH.ISO-8859-1... done
de_DE.UTF-8... done
de_DE.ISO-8859-1... done
de_DE.ISO-8859-15@euro... done
de_IT.UTF-8... done
de_IT.ISO-8859-1... done
de_LI.UTF-8... done
de_LU.UTF-8... done
de_LU.ISO-8859-1... done
de_LU.ISO-8859-15@euro... done
doi_IN.UTF-8... done
dv_MV.UTF-8... done
dz_BT.UTF-8... done
el_GR.UTF-8... done
el_GR.ISO-8859-7... done
el_GR.ISO-8859-7@euro... done
el_CY.UTF-8... done
el_CY.ISO-8859-7... done
en_AG.UTF-8... done
en_AU.UTF-8... done
en_AU.ISO-8859-1... done
en_BW.UTF-8... done
en_BW.ISO-8859-1... done
en_CA.UTF-8... done
en_CA.ISO-8859-1... done
en_DK.UTF-8... done
en_DK.ISO-8859-1... done
en_GB.UTF-8... done
en_GB.ISO-8859-1... done
en_HK.UTF-8... done
en_HK.ISO-8859-1... done
en_IE.UTF-8... done
en_IE.ISO-8859-1... done
en_IE.ISO-8859-15@euro... done
en_IL.UTF-8... done
en_IN.UTF-8... done
en_NG.UTF-8... done
en_NZ.UTF-8... done
en_NZ.ISO-8859-1... done
en_PH.UTF-8... done
en_PH.ISO-8859-1... done
en_SC.UTF-8... done
en_SG.UTF-8... done
en_SG.ISO-8859-1... done
en_US.UTF-8... done
en_US.ISO-8859-1... done
en_ZA.UTF-8... done
en_ZA.ISO-8859-1... done
en_ZM.UTF-8... done
en_ZW.UTF-8... done
en_ZW.ISO-8859-1... done
eo.UTF-8... done
es_AR.UTF-8... done
es_AR.ISO-8859-1... done
es_BO.UTF-8... done
es_BO.ISO-8859-1... done
es_CL.UTF-8... done
es_CL.ISO-8859-1... done
es_CO.UTF-8... done
es_CO.ISO-8859-1... done
es_CR.UTF-8... done
es_CR.ISO-8859-1... done
es_CU.UTF-8... done
es_DO.UTF-8... done
es_DO.ISO-8859-1... done
es_EC.UTF-8... done
es_EC.ISO-8859-1... done
es_ES.UTF-8... done
es_ES.ISO-8859-1... done
es_ES.ISO-8859-15@euro... done
es_GT.UTF-8... done
es_GT.ISO-8859-1... done
es_HN.UTF-8... done
es_HN.ISO-8859-1... done
es_MX.UTF-8... done
es_MX.ISO-8859-1... done
es_NI.UTF-8... done
es_NI.ISO-8859-1... done
es_PA.UTF-8... done
es_PA.ISO-8859-1... done
es_PE.UTF-8... done
es_PE.ISO-8859-1... done
es_PR.UTF-8... done
es_PR.ISO-8859-1... done
es_PY.UTF-8... done
es_PY.ISO-8859-1... done
es_SV.UTF-8... done
es_SV.ISO-8859-1... done
es_US.UTF-8... done
es_US.ISO-8859-1... done
es_UY.UTF-8... done
es_UY.ISO-8859-1... done
es_VE.UTF-8... done
es_VE.ISO-8859-1... done
et_EE.UTF-8... done
et_EE.ISO-8859-1... done
et_EE.ISO-8859-15... done
eu_ES.UTF-8... done
eu_ES.ISO-8859-1... done
eu_ES.ISO-8859-15@euro... done
fa_IR.UTF-8... done
ff_SN.UTF-8... done
fi_FI.UTF-8... done
fi_FI.ISO-8859-1... done
fi_FI.ISO-8859-15@euro... done
fil_PH.UTF-8... done
fo_FO.UTF-8... done
fo_FO.ISO-8859-1... done
fr_BE.UTF-8... done
fr_BE.ISO-8859-1... done
fr_BE.ISO-8859-15@euro... done
fr_CA.UTF-8... done
fr_CA.ISO-8859-1... done
fr_CH.UTF-8... done
fr_CH.ISO-8859-1... done
fr_FR.UTF-8... done
fr_FR.ISO-8859-1... done
fr_FR.ISO-8859-15@euro... done
fr_LU.UTF-8... done
fr_LU.ISO-8859-1... done
fr_LU.ISO-8859-15@euro... done
fur_IT.UTF-8... done
fy_NL.UTF-8... done
fy_DE.UTF-8... done
ga_IE.UTF-8... done
ga_IE.ISO-8859-1... done
ga_IE.ISO-8859-15@euro... done
gd_GB.UTF-8... done
gd_GB.ISO-8859-15... done
gez_ER.UTF-8... done
gez_ER.UTF-8@abegede... done
gez_ET.UTF-8... done
gez_ET.UTF-8@abegede... done
gl_ES.UTF-8... done
gl_ES.ISO-8859-1... done
gl_ES.ISO-8859-15@euro... done
gu_IN.UTF-8... done
gv_GB.UTF-8... done
gv_GB.ISO-8859-1... done
ha_NG.UTF-8... done
hak_TW.UTF-8... done
he_IL.UTF-8... done
he_IL.ISO-8859-8... done
hi_IN.UTF-8... done
hif_FJ.UTF-8... done
hne_IN.UTF-8... done
hr_HR.UTF-8... done
hr_HR.ISO-8859-2... done
hsb_DE.ISO-8859-2... done
hsb_DE.UTF-8... done
ht_HT.UTF-8... done
hu_HU.UTF-8... done
hu_HU.ISO-8859-2... done
hy_AM.UTF-8... done
hy_AM.ARMSCII-8... done
ia_FR.UTF-8... done
id_ID.UTF-8... done
id_ID.ISO-8859-1... done
ig_NG.UTF-8... done
ik_CA.UTF-8... done
is_IS.UTF-8... done
is_IS.ISO-8859-1... done
it_CH.UTF-8... done
it_CH.ISO-8859-1... done
it_IT.UTF-8... done
it_IT.ISO-8859-1... done
it_IT.ISO-8859-15@euro... done
iu_CA.UTF-8... done
ja_JP.EUC-JP... done
ja_JP.UTF-8... done
ka_GE.UTF-8... done
ka_GE.GEORGIAN-PS... done
kk_KZ.UTF-8... done
kk_KZ.PT154... done
kl_GL.UTF-8... done
kl_GL.ISO-8859-1... done
km_KH.UTF-8... done
kn_IN.UTF-8... done
ko_KR.EUC-KR... done
ko_KR.UTF-8... done
kok_IN.UTF-8... done
ks_IN.UTF-8... done
ks_IN.UTF-8@devanagari... done
ku_TR.UTF-8... done
ku_TR.ISO-8859-9... done
kw_GB.UTF-8... done
kw_GB.ISO-8859-1... done
ky_KG.UTF-8... done
lb_LU.UTF-8... done
lg_UG.UTF-8... done
lg_UG.ISO-8859-10... done
li_BE.UTF-8... done
li_NL.UTF-8... done
lij_IT.UTF-8... done
ln_CD.UTF-8... done
lo_LA.UTF-8... done
lt_LT.UTF-8... done
lt_LT.ISO-8859-13... done
lv_LV.UTF-8... done
lv_LV.ISO-8859-13... done
lzh_TW.UTF-8... done
mag_IN.UTF-8... done
mai_IN.UTF-8... done
mai_NP.UTF-8... done
mfe_MU.UTF-8... done
mg_MG.UTF-8... done
mg_MG.ISO-8859-15... done
mhr_RU.UTF-8... done
mi_NZ.UTF-8... done
mi_NZ.ISO-8859-13... done
miq_NI.UTF-8... done
mk_MK.UTF-8... done
mk_MK.ISO-8859-5... done
ml_IN.UTF-8... done
mn_MN.UTF-8... done
mni_IN.UTF-8... done
mr_IN.UTF-8... done
ms_MY.UTF-8... done
ms_MY.ISO-8859-1... done
mt_MT.UTF-8... done
mt_MT.ISO-8859-3... done
my_MM.UTF-8... done
nan_TW.UTF-8... done
nan_TW.UTF-8@latin... done
nb_NO.UTF-8... done
nb_NO.ISO-8859-1... done
nds_DE.UTF-8... done
nds_NL.UTF-8... done
ne_NP.UTF-8... done
nhn_MX.UTF-8... done
niu_NU.UTF-8... done
niu_NZ.UTF-8... done
nl_AW.UTF-8... done
nl_BE.UTF-8... done
nl_BE.ISO-8859-1... done
nl_BE.ISO-8859-15@euro... done
nl_NL.UTF-8... done
nl_NL.ISO-8859-1... done
nl_NL.ISO-8859-15@euro... done
nn_NO.UTF-8... done
nn_NO.ISO-8859-1... done
nr_ZA.UTF-8... done
nso_ZA.UTF-8... done
oc_FR.UTF-8... done
oc_FR.ISO-8859-1... done
om_ET.UTF-8... done
om_KE.UTF-8... done
om_KE.ISO-8859-1... done
or_IN.UTF-8... done
os_RU.UTF-8... done
pa_IN.UTF-8... done
pa_PK.UTF-8... done
pap_AW.UTF-8... done
pap_CW.UTF-8... done
pl_PL.UTF-8... done
pl_PL.ISO-8859-2... done
ps_AF.UTF-8... done
pt_BR.UTF-8... done
pt_BR.ISO-8859-1... done
pt_PT.UTF-8... done
pt_PT.ISO-8859-1... done
pt_PT.ISO-8859-15@euro... done
quz_PE.UTF-8... done
raj_IN.UTF-8... done
ro_RO.UTF-8... done
ro_RO.ISO-8859-2... done
ru_RU.KOI8-R... done
ru_RU.UTF-8... done
ru_RU.ISO-8859-5... done
ru_UA.UTF-8... done
ru_UA.KOI8-U... done
rw_RW.UTF-8... done
sa_IN.UTF-8... done
sat_IN.UTF-8... done
sc_IT.UTF-8... done
sd_IN.UTF-8... done
sd_IN.UTF-8@devanagari... done
se_NO.UTF-8... done
sgs_LT.UTF-8... done
shs_CA.UTF-8... done
si_LK.UTF-8... done
sid_ET.UTF-8... done
sk_SK.UTF-8... done
sk_SK.ISO-8859-2... done
sl_SI.UTF-8... done
sl_SI.ISO-8859-2... done
sm_WS.UTF-8... done
so_DJ.UTF-8... done
so_DJ.ISO-8859-1... done
so_ET.UTF-8... done
so_KE.UTF-8... done
so_KE.ISO-8859-1... done
so_SO.UTF-8... done
so_SO.ISO-8859-1... done
sq_AL.UTF-8... done
sq_AL.ISO-8859-1... done
sq_MK.UTF-8... done
sr_ME.UTF-8... done
sr_RS.UTF-8... done
sr_RS.UTF-8@latin... done
ss_ZA.UTF-8... done
st_ZA.UTF-8... done
st_ZA.ISO-8859-1... done
sv_FI.UTF-8... done
sv_FI.ISO-8859-1... done
sv_FI.ISO-8859-15@euro... done
sv_SE.UTF-8... done
sv_SE.ISO-8859-1... done
sw_KE.UTF-8... done
sw_TZ.UTF-8... done
szl_PL.UTF-8... done
ta_IN.UTF-8... done
ta_LK.UTF-8... done
tcy_IN.UTF-8... done
te_IN.UTF-8... done
tg_TJ.UTF-8... done
tg_TJ.KOI8-T... done
th_TH.UTF-8... done
th_TH.TIS-620... done
the_NP.UTF-8... done
ti_ER.UTF-8... done
ti_ET.UTF-8... done
tig_ER.UTF-8... done
tk_TM.UTF-8... done
tl_PH.UTF-8... done
tl_PH.ISO-8859-1... done
tn_ZA.UTF-8... done
to_TO.UTF-8... done
tpi_PG.UTF-8... done
tr_CY.UTF-8... done
tr_CY.ISO-8859-9... done
tr_TR.UTF-8... done
tr_TR.ISO-8859-9... done
ts_ZA.UTF-8... done
tt_RU.UTF-8... done
tt_RU.UTF-8@iqtelif... done
ug_CN.UTF-8... done
uk_UA.UTF-8... done
uk_UA.KOI8-U... done
unm_US.UTF-8... done
ur_IN.UTF-8... done
ur_PK.UTF-8... done
uz_UZ.UTF-8... done
uz_UZ.ISO-8859-1... done
uz_UZ.UTF-8@cyrillic... done
ve_ZA.UTF-8... done
vi_VN.UTF-8... done
wa_BE.ISO-8859-1... done
wa_BE.ISO-8859-15@euro... done
wa_BE.UTF-8... done
wae_CH.UTF-8... done
wal_ET.UTF-8... done
wo_SN.UTF-8... done
xh_ZA.UTF-8... done
xh_ZA.ISO-8859-1... done
yi_US.UTF-8... done
yi_US.CP1255... done
yo_NG.UTF-8... done
yue_HK.UTF-8... done
zh_CN.GB18030... done
zh_CN.GBK... done
zh_CN.UTF-8... done
zh_CN.GB2312... done
zh_HK.UTF-8... done
zh_HK.BIG5-HKSCS... done
zh_SG.UTF-8... done
zh_SG.GBK... done
zh_SG.GB2312... done
zh_TW.EUC-TW... done
zh_TW.UTF-8... done
zh_TW.BIG5... done
zu_ZA.UTF-8... done
zu_ZA.ISO-8859-1... done
make[2]: Leaving directory '/mnt/ssd/carlos/src/glibc-work/localedata'
make[1]: Leaving directory '/mnt/ssd/carlos/src/glibc-work'
[carlos@athas glibc-work]$ echo $?
0

This isn't *quite* a clean tree, I have 2 commits on top of this to
cleanup verbose messages, and reorder the test names (patch posted
already).
  
Andreas Schwab Oct. 15, 2017, 9:01 p.m. UTC | #9
On Okt 15 2017, Carlos O'Donell <carlos@redhat.com> wrote:

> On 10/15/2017 02:14 AM, Andreas Schwab wrote:
>> On Okt 13 2017, Carlos O'Donell <carlos@redhat.com> wrote:
>> 
>>> This has allowed the following fix:
>>> * Previously any warnings were being treated as errors
>>>   because they incremented error_message_count, but now
>>>   we properly return an exit status of 1 if there are
>>>   warnings but output was generated.
>> 
>> This breaks make localedata/install-locales.
>
> What breakage are you seeing?

https://build.opensuse.org/package/live_build_log/home:Andreas_Schwab:glibc/glibc/f/x86_64

Andreas.
  
Carlos O'Donell Oct. 15, 2017, 9:19 p.m. UTC | #10
On 10/15/2017 02:01 PM, Andreas Schwab wrote:
> On Okt 15 2017, Carlos O'Donell <carlos@redhat.com> wrote:
> 
>> On 10/15/2017 02:14 AM, Andreas Schwab wrote:
>>> On Okt 13 2017, Carlos O'Donell <carlos@redhat.com> wrote:
>>>
>>>> This has allowed the following fix:
>>>> * Previously any warnings were being treated as errors
>>>>   because they incremented error_message_count, but now
>>>>   we properly return an exit status of 1 if there are
>>>>   warnings but output was generated.
>>>
>>> This breaks make localedata/install-locales.
>>
>> What breakage are you seeing?
> 
> https://build.opensuse.org/package/live_build_log/home:Andreas_Schwab:glibc/glibc/f/x86_64

Thank you for the context. That is very helpful.

You are building a locale/charmap combination that is not supported 
(in the SUPPORTED file) and has a warning. The warning in this case
is that SHIFT_JIS and SHIFT_JISX0213 are not ASCII compatible.

POSIX requires that if we issue a warning, and still output the locale
anyway, that the error code is 1.

If you are building non-standard locale/charmap combinations, and we
want to make localedata/install-locales to work, then we need to do one
of two things:

(a) Support having locales that have warnings (and adjust our install
    process to accept 0 or 1 as a valid return code from localedef).

(b) Do not make ASCII compatibility a warning. See the large comment
    in locale/programs/charmap.c line ~205. It would appear that the
    SHIFT_JIS* charmaps violate this ISO C requirement, which could
    cause problems for some programs, thus the warning
    (in localedata/gen-locale.sh I work around this for SHIFT_SJIS
    for testing shift charmaps).

Only now with proper warning/errors in place can we reliably get to
the point about having this discussion (previous to my fixes it was
a mess).

Which of (a) or (b) do you prefer?
  
Andreas Schwab Oct. 15, 2017, 9:34 p.m. UTC | #11
On Okt 15 2017, Carlos O'Donell <carlos@redhat.com> wrote:

> Which of (a) or (b) do you prefer?

I don't really have a preference.

Andreas.
  
Florian Weimer Oct. 16, 2017, 7:24 a.m. UTC | #12
On 10/15/2017 11:19 PM, Carlos O'Donell wrote:
> (b) Do not make ASCII compatibility a warning. See the large comment
>      in locale/programs/charmap.c line ~205. It would appear that the
>      SHIFT_JIS* charmaps violate this ISO C requirement, which could
>      cause problems for some programs, thus the warning
>      (in localedata/gen-locale.sh I work around this for SHIFT_SJIS
>      for testing shift charmaps).

Running a system with Shift JIS may introduce security vulnerabilities 
because parts of the system will disagree where string literals end, due 
different interpretations of the 0x5c codepoint.

I think we should add a command line argument to localedef which turns 
off returning exit status 1 for locales which are not ASCII-compatible.

Thanks,
Florian
  
Carlos O'Donell Oct. 16, 2017, 7:30 p.m. UTC | #13
On 10/16/2017 12:24 AM, Florian Weimer wrote:
> On 10/15/2017 11:19 PM, Carlos O'Donell wrote:
>> (b) Do not make ASCII compatibility a warning. See the large
>> comment in locale/programs/charmap.c line ~205. It would appear
>> that the SHIFT_JIS* charmaps violate this ISO C requirement, which
>> could cause problems for some programs, thus the warning (in
>> localedata/gen-locale.sh I work around this for SHIFT_SJIS for
>> testing shift charmaps).
> 
> Running a system with Shift JIS may introduce security
> vulnerabilities because parts of the system will disagree where
> string literals end, due different interpretations of the 0x5c
> codepoint.
> 
> I think we should add a command line argument to localedef which
> turns off returning exit status 1 for locales which are not
> ASCII-compatible.

Done. I'll work that up and post a patch.
  

Patch

From 42e77578ff74c30c6cfe94dd5ed1b495715c084c Mon Sep 17 00:00:00 2001
From: Carlos O'Donell <carlos@systemhalted.org>
Date: Fri, 13 Oct 2017 09:54:03 -0700
Subject: [PATCH] locale: Fix localedef exit code (Bug 22292)

The error and warning handling in localedef, locale, and iconv
is a bit of a mess.

We use ugly constructs like this:
      WITH_CUR_LOCALE (error (1, errno, gettext ("\
cannot read character map directory `%s'"), directory));

to issue errors, and read error_message_count directly from the
error API to detect errors. The problem with that is that the
code also uses error to print warnings, and informative messages.
All of this leads to problems where just having warnings will
produce an exit status as-if errors had been seen.

To fix this situation I have adopted the following high-level
changes:
* All errors are counted distinctly.
* All warnings are counted distinctly.
* All informative messages are not counted.
* Increasing verbosity cannot generate *more* errors, and
  it previously did for errors conditional on verbose,
  this is now fixed.
* Increasing verbosity *can* generate *more* warnings.
* Making the output quiet cannot generate *fewer* errors,
  and it previously did for errors conditional on be_quiet,
  this is now fixed.
* Each of error, warning, and informative message has it's
  own function to call defined in record-status.h, and they
  are: record_error, record_warning, and record_verbose.
* The record_error function always records an error, but
  conditional on be_quiet may not print it.
* The record_warning function always records a warning,
  but conditional on be_quiet may not print it.
* The record_verbose function only prints the verbose
  message if verbose is true and be_quiet is false.

This has allowed the following fix:
* Previously any warnings were being treated as errors
  because they incremented error_message_count, but now
  we properly return an exit status of 1 if there are
  warnings but output was generated.

All of this allows localedef to correctly decide if errors,
or warnings were present, and produce the correct exit code.

The locale and iconv programs now also use record-status.h
and we have removed the WITH_CUR_LOCALE hack, and instead
have internal push_locale/pop_locale functions centralized
in the record routines.

Signed-off-by: Carlos O'Donell <carlos@redhat.com>
---
 ChangeLog                           |  54 ++++++++
 locale/programs/charmap-dir.c       |   6 +-
 locale/programs/charmap.c           |  35 +++--
 locale/programs/ld-address.c        |  89 ++++++-------
 locale/programs/ld-collate.c        |  62 ++++-----
 locale/programs/ld-ctype.c          | 258 ++++++++++++++++--------------------
 locale/programs/ld-identification.c |  24 ++--
 locale/programs/ld-measurement.c    |  14 +-
 locale/programs/ld-messages.c       |  35 +++--
 locale/programs/ld-monetary.c       |  57 ++++----
 locale/programs/ld-name.c           |  20 ++-
 locale/programs/ld-numeric.c        |  23 ++--
 locale/programs/ld-paper.c          |  14 +-
 locale/programs/ld-telephone.c      |  26 ++--
 locale/programs/ld-time.c           |  97 ++++++--------
 locale/programs/linereader.h        |  26 +++-
 locale/programs/localedef.c         |  62 ++++++---
 locale/programs/localedef.h         |  18 +--
 locale/programs/locarchive.c        |   4 +-
 locale/programs/locfile.c           |  15 +--
 locale/programs/record-status.h     | 229 ++++++++++++++++++++++++++++++++
 locale/programs/repertoire.c        |  11 +-
 22 files changed, 707 insertions(+), 472 deletions(-)
 create mode 100644 locale/programs/record-status.h

diff --git a/ChangeLog b/ChangeLog
index 6b3a98c..d4fb088 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,57 @@ 
+2017-10-13  Carlos O'Donell  <carlos@redhat.com>
+
+	[BZ #22292]
+	* locale/programs/record-status.h: New file
+	* locale/programs/charmap-dir.c: Don't include error.h.
+	(charmap_opendir): Use record_error.
+	* locale/programs/charmap.c: Don't include error.h.
+	(charmap_read): Use record_error, and record_warning.
+	(parse_charmap): Likewise.
+	* locale/programs/ld-address.c: Don't include error.h.
+	(address_finish): Use record_error, and record_warning.
+	* locale/programs/ld-collate.c: Don't include error.h.
+	(collate_finish): Use record_error, and record_error_at_line.
+	* locale/programs/ld-ctype.c (ctype_finish): Use record_error.
+	(ctype_class_new): Likewise.
+	(ctype_map_new): Likewise.
+	(set_one_default): Likewise.
+	(set_class_defaults): Likewise.
+	(translit_flatten): Likewise.
+	(allocate_arrays): Use record_error, and record_verbose.
+	* locale/programs/ld-identification.c: Don't include error.h.
+	(indentation_finish): Use record_error and record_warning.
+	* locale/programs/ld-measurement.c: Don't include error.h.
+	(measurement_finish): Use record_error.
+	* locale/programs/ld-messages.c
+	(message_finish): Likewise.
+	* locale/programs/ld-monetary.c
+	(monetary_finish): Likewise.
+	* locale/programs/ld-name.c (name_finish): Use record_error
+	and record_warning.
+	* locale/programs/ld-numeric.c
+	(numeric_finish): Use record_error.
+	* locale/programs/ld-paper.c: Don't include error.h.
+	(paper_finish): Use record_error.
+	* locale/programs/ld-telephone.c: Don't include error.h.
+	(telephone_finish): Use record_error.
+	* locale/programs/ld-time.c (time_finish): Likewise.
+	* locale/programs/linereader.h (lr_error): Make inline func.
+	* locale/programs/localedef.c: Define recorded_warning_count,
+	and recorded_error_count.
+	(main): Use record_error. Use recorded_error_count and
+	recorded_warning_count to issue correct error returns.
+	(add_to_readlist): Use record_error.
+	(find_locale): Likewise.
+	(load_locale): Likewise.
+	* locale/programs/localedef.h: Remove be_quiet
+	and WITH_CUR_LOCALE.
+	* locale/programs/locarchive.c (compare_from_file): Use
+	record_error.
+	* locale/programs/locfile.c (write_locale_data): Use
+	record_error.
+	* locale/programs/repertoire.c: Dont include error.h.
+	(repertoire_complain): Use record_error.
+
 2017-10-12  Carlos O'Donell  <carlos@redhat.com>
 
 	* localedata/unicode-gen/Makefile (GENERATED): Use i18n_ctype.
diff --git a/locale/programs/charmap-dir.c b/locale/programs/charmap-dir.c
index e55ab86..a9212b7 100644
--- a/locale/programs/charmap-dir.c
+++ b/locale/programs/charmap-dir.c
@@ -16,7 +16,6 @@ 
 
 #include <dirent.h>
 #include <errno.h>
-#include <error.h>
 #include <fcntl.h>
 #include <libintl.h>
 #include <spawn.h>
@@ -54,8 +53,9 @@  charmap_opendir (const char *directory)
   dir = opendir (directory);
   if (dir == NULL)
     {
-      WITH_CUR_LOCALE (error (1, errno, gettext ("\
-cannot read character map directory `%s'"), directory));
+      record_error (1, errno, gettext ("\
+cannot read character map directory `%s'"),
+		    directory);
       return NULL;
     }
 
diff --git a/locale/programs/charmap.c b/locale/programs/charmap.c
index 129aeff..a670db9 100644
--- a/locale/programs/charmap.c
+++ b/locale/programs/charmap.c
@@ -26,7 +26,6 @@ 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <error.h>
 #include <stdint.h>
 
 #include "localedef.h"
@@ -135,8 +134,9 @@  charmap_read (const char *filename, int verbose, int error_not_found,
 	result = parse_charmap (cmfile, verbose, be_quiet);
 
       if (result == NULL && error_not_found)
-	WITH_CUR_LOCALE (error (0, errno, _("\
-character map file `%s' not found"), filename));
+	record_error (0, errno,
+		      _("character map file `%s' not found"),
+		      filename);
     }
 
   if (result == NULL && filename != NULL && strchr (filename, '/') == NULL)
@@ -192,8 +192,9 @@  character map file `%s' not found"), filename));
 	result = parse_charmap (cmfile, verbose, be_quiet);
 
       if (result == NULL)
-	WITH_CUR_LOCALE (error (4, errno, _("\
-default character map file `%s' not found"), DEFAULT_CHARMAP));
+	record_error (4, errno,
+		      _("default character map file `%s' not found"),
+		      DEFAULT_CHARMAP);
     }
 
   if (result != NULL && result->code_set_name == NULL)
@@ -255,9 +256,9 @@  default character map file `%s' not found"), DEFAULT_CHARMAP));
 
       if (failed)
 	{
-	  WITH_CUR_LOCALE (fprintf (stderr, _("\
+	  record_warning (_("\
 character map `%s' is not ASCII compatible, locale not ISO C compliant\n"),
-				    result->code_set_name));
+			  result->code_set_name);
 	  enc_not_ascii_compatible = true;
 	}
     }
@@ -333,10 +334,9 @@  parse_charmap (struct linereader *cmfile, int verbose, int be_quiet)
 		result->mb_cur_min = result->mb_cur_max;
 	      if (result->mb_cur_min > result->mb_cur_max)
 		{
-		  if (!be_quiet)
-		    WITH_CUR_LOCALE (error (0, 0, _("\
+		  record_error (0, 0, _("\
 %s: <mb_cur_max> must be greater than <mb_cur_min>\n"),
-					    cmfile->fname));
+				cmfile->fname);
 
 		  result->mb_cur_min = result->mb_cur_max;
 		}
@@ -395,11 +395,10 @@  parse_charmap (struct linereader *cmfile, int verbose, int be_quiet)
 	      if (arg->tok != tok_number)
 		goto badarg;
 
-	      if (verbose
-		  && ((nowtok == tok_mb_cur_max
+	      if ((nowtok == tok_mb_cur_max
 		       && result->mb_cur_max != 0)
 		      || (nowtok == tok_mb_cur_max
-			  && result->mb_cur_max != 0)))
+			  && result->mb_cur_max != 0))
 		lr_error (cmfile, _("duplicate definition of <%s>"),
 			  nowtok == tok_mb_cur_min
 			  ? "mb_cur_min" : "mb_cur_max");
@@ -839,16 +838,16 @@  only WIDTH definitions are allowed to follow the CHARMAP definition"));
 	  continue;
 
 	default:
-	  WITH_CUR_LOCALE (error (5, 0, _("%s: error in state machine"),
-				  __FILE__));
+	  record_error (5, 0, _("%s: error in state machine"),
+			__FILE__);
 	  /* NOTREACHED */
 	}
       break;
     }
 
-  if (state != 91 && !be_quiet)
-    WITH_CUR_LOCALE (error (0, 0, _("%s: premature end of file"),
-			    cmfile->fname));
+  if (state != 91)
+    record_error (0, 0, _("%s: premature end of file"),
+		  cmfile->fname);
 
   lr_close (cmfile);
 
diff --git a/locale/programs/ld-address.c b/locale/programs/ld-address.c
index 2488a5c..24620f0 100644
--- a/locale/programs/ld-address.c
+++ b/locale/programs/ld-address.c
@@ -20,7 +20,6 @@ 
 #endif
 
 #include <byteswap.h>
-#include <error.h>
 #include <langinfo.h>
 #include <string.h>
 #include <stdint.h>
@@ -132,8 +131,8 @@  address_finish (struct localedef_t *locale, const struct charmap_t *charmap)
       if (address == NULL)
 	{
 	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_ADDRESS"));
+	    record_error (0, 0, _("\
+No definition for %s category found"), "LC_ADDRESS");
 	  address_startup (NULL, locale, 0);
 	  address = locale->categories[LC_ADDRESS].address;
 	  nothing = 1;
@@ -143,8 +142,8 @@  No definition for %s category found"), "LC_ADDRESS"));
   if (address->postal_fmt == NULL)
     {
       if (! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_ADDRESS", "postal_fmt"));
+	record_error (0, 0, _("%s: field `%s' not defined"),
+		      "LC_ADDRESS", "postal_fmt");
       /* Use as the default value the value of the i18n locale.  */
       address->postal_fmt = "%a%N%f%N%d%N%b%N%s %h %e %r%N%C-%z %T%N%c%N";
     }
@@ -155,8 +154,8 @@  No definition for %s category found"), "LC_ADDRESS"));
       const char *cp = address->postal_fmt;
 
       if (*cp == '\0')
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' must not be empty"),
-				"LC_ADDRESS", "postal_fmt"));
+	record_error (0, 0, _("%s: field `%s' must not be empty"),
+		      "LC_ADDRESS", "postal_fmt");
       else
 	while (*cp != '\0')
 	  {
@@ -167,9 +166,9 @@  No definition for %s category found"), "LC_ADDRESS"));
 		  ++cp;
 		if (strchr ("nafdbshNtreClzTSc%", *cp) == NULL)
 		  {
-		    WITH_CUR_LOCALE (error (0, 0, _("\
+		    record_error (0, 0, _("\
 %s: invalid escape `%%%c' sequence in field `%s'"),
-					    "LC_ADDRESS", *cp, "postal_fmt"));
+				  "LC_ADDRESS", *cp, "postal_fmt");
 		    break;
 		  }
 	      }
@@ -181,8 +180,7 @@  No definition for %s category found"), "LC_ADDRESS"));
   if (address->cat == NULL)						      \
     {									      \
       if (verbose && ! nothing)						      \
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),	      \
-				"LC_ADDRESS", #cat));  	    		      \
+	record_warning (_("%s: field `%s' not defined"), "LC_ADDRESS", #cat); \
       address->cat = "";						      \
     }
 
@@ -199,16 +197,16 @@  No definition for %s category found"), "LC_ADDRESS"));
   if (address->lang_term == NULL)
     {
       if (verbose && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_ADDRESS", "lang_term"));
+	record_warning (_("%s: field `%s' not defined"), "LC_ADDRESS",
+			"lang_term");
       address->lang_term = "";
       cnt = sizeof (iso639) / sizeof (iso639[0]);
     }
   else if (address->lang_term[0] == '\0')
     {
       if (verbose)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' must not be empty"),
-				"LC_ADDRESS", "lang_term"));
+	record_warning (_("%s: field `%s' must not be empty"), "LC_ADDRESS",
+			"lang_term");
       cnt = sizeof (iso639) / sizeof (iso639[0]);
     }
   else
@@ -218,9 +216,9 @@  No definition for %s category found"), "LC_ADDRESS"));
 	if (strcmp (address->lang_term, iso639[cnt].term) == 0)
 	  break;
       if (cnt == sizeof (iso639) / sizeof (iso639[0]))
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: terminology language code `%s' not defined"),
-				"LC_ADDRESS", address->lang_term));
+		      "LC_ADDRESS", address->lang_term);
     }
 
   if (address->lang_ab == NULL)
@@ -228,8 +226,8 @@  No definition for %s category found"), "LC_ADDRESS"));
       if ((cnt == sizeof (iso639) / sizeof (iso639[0])
 	   || iso639[cnt].ab[0] != '\0')
 	  && verbose && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_ADDRESS", "lang_ab"));
+	record_warning (_("%s: field `%s' not defined"), "LC_ADDRESS",
+			"lang_ab");
       address->lang_ab = "";
     }
   else if (address->lang_ab[0] == '\0')
@@ -237,14 +235,14 @@  No definition for %s category found"), "LC_ADDRESS"));
       if ((cnt == sizeof (iso639) / sizeof (iso639[0])
 	   || iso639[cnt].ab[0] != '\0')
 	  && verbose)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' must not be empty"),
-				"LC_ADDRESS", "lang_ab"));
+	record_warning (_("%s: field `%s' must not be empty"),
+			"LC_ADDRESS", "lang_ab");
     }
   else if (cnt < sizeof (iso639) / sizeof (iso639[0])
 	   && iso639[cnt].ab[0] == '\0')
     {
-      WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' must not be defined"),
-			      "LC_ADDRESS", "lang_ab"));
+      record_error (0, 0, _("%s: field `%s' must not be defined"),
+		    "LC_ADDRESS", "lang_ab");
 
       address->lang_ab = "";
     }
@@ -257,16 +255,16 @@  No definition for %s category found"), "LC_ADDRESS"));
 	    if (strcmp (address->lang_ab, iso639[cnt].ab) == 0)
 	      break;
 	  if (cnt == sizeof (iso639) / sizeof (iso639[0]))
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	    record_error (0, 0, _("\
 %s: language abbreviation `%s' not defined"),
-				    "LC_ADDRESS", address->lang_ab));
+			  "LC_ADDRESS", address->lang_ab);
 	}
       else
 	if (strcmp (iso639[cnt].ab, address->lang_ab) != 0
 	    && iso639[cnt].ab[0] != '\0')
-	  WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: `%s' value does not match `%s' value"),
-				  "LC_ADDRESS", "lang_ab", "lang_term"));
+			"LC_ADDRESS", "lang_ab", "lang_term");
     }
 
   if (address->lang_lib == NULL)
@@ -275,8 +273,8 @@  No definition for %s category found"), "LC_ADDRESS"));
   else if (address->lang_lib[0] == '\0')
     {
       if (verbose)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' must not be empty"),
-				"LC_ADDRESS", "lang_lib"));
+	record_warning (_("%s: field `%s' must not be empty"),
+			"LC_ADDRESS", "lang_lib");
     }
   else
     {
@@ -286,22 +284,22 @@  No definition for %s category found"), "LC_ADDRESS"));
 	    if (strcmp (address->lang_lib, iso639[cnt].lib) == 0)
 	      break;
 	  if (cnt == sizeof (iso639) / sizeof (iso639[0]))
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	    record_error (0, 0, _("\
 %s: language abbreviation `%s' not defined"),
-				    "LC_ADDRESS", address->lang_lib));
+			  "LC_ADDRESS", address->lang_lib);
 	}
       else
 	if (strcmp (iso639[cnt].ab, address->lang_ab) != 0)
-	  WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: `%s' value does not match `%s' value"), "LC_ADDRESS", "lang_lib",
-				  helper == 1 ? "lang_term" : "lang_ab"));
+			helper == 1 ? "lang_term" : "lang_ab");
     }
 
   if (address->country_num == 0)
     {
       if (verbose && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_ADDRESS", "country_num"));
+	record_warning (_("%s: field `%s' not defined"), "LC_ADDRESS",
+			"country_num");
       cnt = sizeof (iso3166) / sizeof (iso3166[0]);
     }
   else
@@ -311,36 +309,35 @@  No definition for %s category found"), "LC_ADDRESS"));
 	  break;
 
       if (cnt == sizeof (iso3166) / sizeof (iso3166[0]))
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: numeric country code `%d' not valid"),
-				"LC_ADDRESS", address->country_num));
+		      "LC_ADDRESS", address->country_num);
     }
 
   if (address->country_ab2 == NULL)
     {
       if (verbose && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_ADDRESS", "country_ab2"));
+	record_warning (_("%s: field `%s' not defined"), "LC_ADDRESS",
+			"country_ab2");
       address->country_ab2 = "  ";
     }
   else if (cnt != sizeof (iso3166) / sizeof (iso3166[0])
 	   && strcmp (address->country_ab2, iso3166[cnt].ab2) != 0)
-    WITH_CUR_LOCALE (error (0, 0,
-			    _("%s: `%s' value does not match `%s' value"),
-			    "LC_ADDRESS", "country_ab2", "country_num"));
+    record_error (0, 0, _("%s: `%s' value does not match `%s' value"),
+		  "LC_ADDRESS", "country_ab2", "country_num");
 
   if (address->country_ab3 == NULL)
     {
       if (verbose && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_ADDRESS", "country_ab3"));
+	record_warning (_("%s: field `%s' not defined"), "LC_ADDRESS",
+			"country_ab3");
       address->country_ab3 = "   ";
     }
   else if (cnt != sizeof (iso3166) / sizeof (iso3166[0])
 	   && strcmp (address->country_ab3, iso3166[cnt].ab3) != 0)
-    WITH_CUR_LOCALE (error (0, 0, _("\
+    record_error (0, 0, _("\
 %s: `%s' value does not match `%s' value"),
-			    "LC_ADDRESS", "country_ab3", "country_num"));
+		  "LC_ADDRESS", "country_ab3", "country_num");
 }
 
 
diff --git a/locale/programs/ld-collate.c b/locale/programs/ld-collate.c
index cec848c..18df4e0 100644
--- a/locale/programs/ld-collate.c
+++ b/locale/programs/ld-collate.c
@@ -20,7 +20,6 @@ 
 #endif
 
 #include <errno.h>
-#include <error.h>
 #include <stdlib.h>
 #include <wchar.h>
 #include <stdint.h>
@@ -1561,9 +1560,8 @@  collate_finish (struct localedef_t *locale, const struct charmap_t *charmap)
   if (collate == NULL)
     {
       /* No data, no check.  */
-      if (! be_quiet)
-	WITH_CUR_LOCALE (error (0, 0, _("No definition for %s category found"),
-				"LC_COLLATE"));
+      record_error (0, 0, _("No definition for %s category found"),
+		    "LC_COLLATE");
       return;
     }
 
@@ -1579,9 +1577,9 @@  collate_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 	  && ((sect->rules[i] & sort_position)
 	      != (collate->current_section->rules[i] & sort_position)))
 	{
-	  WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: `position' must be used for a specific level in all sections or none"),
-				  "LC_COLLATE"));
+			"LC_COLLATE");
 	  break;
 	}
 
@@ -1602,10 +1600,9 @@  collate_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 		  {
 		    if (runp->weights[i].w[j]->weights == NULL)
 		      {
-			WITH_CUR_LOCALE (error_at_line (0, 0, runp->file,
-							runp->line,
-							_("symbol `%s' not defined"),
-							runp->weights[i].w[j]->name));
+			record_error_at_line (0, 0, runp->file, runp->line,
+					      _("symbol `%s' not defined"),
+					      runp->weights[i].w[j]->name);
 
 			need_undefined = 1;
 			runp->weights[i].w[j] = &collate->undefined;
@@ -1678,14 +1675,13 @@  collate_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 		      /* This should not happen.  It means that we have
 			 to symbols with the same byte sequence.  It is
 			 of course an error.  */
-		      WITH_CUR_LOCALE (error_at_line (0, 0, (*eptr)->file,
-						      (*eptr)->line,
-						      _("\
+		      record_error_at_line (0, 0, (*eptr)->file,
+					    (*eptr)->line,
+					    _("\
 symbol `%s' has the same encoding as"), (*eptr)->name);
-				       error_at_line (0, 0, runp->file,
-						      runp->line,
-						      _("symbol `%s'"),
-						      runp->name));
+
+		      record_error_at_line (0, 0, runp->file, runp->line,
+					    _("symbol `%s'"), runp->name);
 		      goto dont_insert;
 		    }
 		  else if (c < 0)
@@ -1784,14 +1780,13 @@  symbol `%s' has the same encoding as"), (*eptr)->name);
 		      /* This should not happen.  It means that we have
 			 two symbols with the same byte sequence.  It is
 			 of course an error.  */
-		      WITH_CUR_LOCALE (error_at_line (0, 0, (*eptr)->file,
-						      (*eptr)->line,
-						      _("\
+		      record_error_at_line (0, 0, (*eptr)->file,
+					    (*eptr)->line,
+					    _("\
 symbol `%s' has the same encoding as"), (*eptr)->name);
-				       error_at_line (0, 0, runp->file,
-						      runp->line,
-						      _("symbol `%s'"),
-						      runp->name));
+
+		      record_error_at_line (0, 0, runp->file, runp->line,
+					    _("symbol `%s'"), runp->name);
 		      goto dont_insertwc;
 		    }
 		  else if (c < 0)
@@ -1829,10 +1824,6 @@  symbol `%s' has the same encoding as"), (*eptr)->name);
 	{
 	  /* This seems not to be enforced by recent standards.  Don't
 	     emit an error, simply append UNDEFINED at the end.  */
-	  if (0)
-	    WITH_CUR_LOCALE (error (0, 0, _("no definition of `UNDEFINED'")));
-
-	  /* Add UNDEFINED at the end.  */
 	  collate->undefined.mborder =
 	    (int *) obstack_alloc (&collate->mempool, nrules * sizeof (int));
 
@@ -1858,8 +1849,7 @@  symbol `%s' has the same encoding as"), (*eptr)->name);
   /* Bail out if we have no sections because of earlier errors.  */
   if (sect == NULL)
     {
-      WITH_CUR_LOCALE (error (EXIT_FAILURE, 0,
-			      _("too many errors; giving up")));
+      record_error (EXIT_FAILURE, 0, _("too many errors; giving up"));
       return;
     }
 
@@ -3408,8 +3398,8 @@  error while adding equivalent collating symbol"));
 	    }
 	  else if (state == 3)
 	    {
-	      WITH_CUR_LOCALE (error (0, 0, _("\
-%s: missing `reorder-end' keyword"), "LC_COLLATE"));
+	      record_error (0, 0, _("\
+%s: missing `reorder-end' keyword"), "LC_COLLATE");
 	      state = 4;
 	    }
 	  else if (state != 2 && state != 4)
@@ -3769,11 +3759,11 @@  error while adding equivalent collating symbol"));
 		    }
 		}
 	      else if (state == 3)
-		WITH_CUR_LOCALE (error (0, 0, _("\
-%s: missing `reorder-end' keyword"), "LC_COLLATE"));
+		record_error (0, 0, _("\
+%s: missing `reorder-end' keyword"), "LC_COLLATE");
 	      else if (state == 5)
-		WITH_CUR_LOCALE (error (0, 0, _("\
-%s: missing `reorder-sections-end' keyword"), "LC_COLLATE"));
+		record_error (0, 0, _("\
+%s: missing `reorder-sections-end' keyword"), "LC_COLLATE");
 	    }
 	  arg = lr_token (ldfile, charmap, result, NULL, verbose);
 	  if (arg->tok == tok_eof)
diff --git a/locale/programs/ld-ctype.c b/locale/programs/ld-ctype.c
index df266c2..284b2a3 100644
--- a/locale/programs/ld-ctype.c
+++ b/locale/programs/ld-ctype.c
@@ -427,9 +427,8 @@  ctype_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 	 empty one.  */
       if (ctype == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_CTYPE"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_CTYPE");
 	  ctype_startup (NULL, locale, charmap, NULL, 0);
 	  ctype = locale->categories[LC_CTYPE].ctype;
 	}
@@ -446,9 +445,8 @@  No definition for %s category found"), "LC_CTYPE"));
   ctype->codeset_name = charmap->code_set_name;
   if (ctype->codeset_name == NULL)
     {
-      if (! be_quiet)
-	WITH_CUR_LOCALE (error (0, 0, _("\
-No character set name specified in charmap")));
+      record_error (0, 0, _("\
+No character set name specified in charmap"));
       ctype->codeset_name = "//UNKNOWN//";
     }
 
@@ -475,13 +473,12 @@  No character set name specified in charmap")));
 			  {
 			    uint32_t value = ctype->charnames[cnt];
 
-			    if (!be_quiet)
-			      WITH_CUR_LOCALE (error (0, 0, _("\
+			    record_error (0, 0, _("\
 character L'\\u%0*x' in class `%s' must be in class `%s'"),
-						      value > 0xffff ? 8 : 4,
-						      value,
-						      valid_table[cls1].name,
-						      valid_table[cls2].name));
+					  value > 0xffff ? 8 : 4,
+					  value,
+					  valid_table[cls1].name,
+					  valid_table[cls2].name);
 			  }
 			break;
 
@@ -490,13 +487,12 @@  character L'\\u%0*x' in class `%s' must be in class `%s'"),
 			  {
 			    uint32_t value = ctype->charnames[cnt];
 
-			    if (!be_quiet)
-			      WITH_CUR_LOCALE (error (0, 0, _("\
+			    record_error (0, 0, _("\
 character L'\\u%0*x' in class `%s' must not be in class `%s'"),
-						      value > 0xffff ? 8 : 4,
-						      value,
-						      valid_table[cls1].name,
-						      valid_table[cls2].name));
+					  value > 0xffff ? 8 : 4,
+					  value,
+					  valid_table[cls1].name,
+					  valid_table[cls2].name);
 			  }
 			break;
 
@@ -505,8 +501,8 @@  character L'\\u%0*x' in class `%s' must not be in class `%s'"),
 			break;
 
 		      default:
-			WITH_CUR_LOCALE (error (5, 0, _("\
-internal error in %s, line %u"), __FUNCTION__, __LINE__));
+			record_error (5, 0, _("\
+internal error in %s, line %u"), __FUNCTION__, __LINE__);
 		      }
 		  }
 	}
@@ -533,12 +529,11 @@  internal error in %s, line %u"), __FUNCTION__, __LINE__));
 
 			    snprintf (buf, sizeof buf, "\\%Zo", cnt);
 
-			    if (!be_quiet)
-			      WITH_CUR_LOCALE (error (0, 0, _("\
+			    record_error (0, 0, _("\
 character '%s' in class `%s' must be in class `%s'"),
-						      buf,
-						      valid_table[cls1].name,
-						      valid_table[cls2].name));
+					  buf,
+					  valid_table[cls1].name,
+					  valid_table[cls2].name);
 			  }
 			break;
 
@@ -549,12 +544,11 @@  character '%s' in class `%s' must be in class `%s'"),
 
 			    snprintf (buf, sizeof buf, "\\%Zo", cnt);
 
-			    if (!be_quiet)
-			      WITH_CUR_LOCALE (error (0, 0, _("\
+			    record_error (0, 0, _("\
 character '%s' in class `%s' must not be in class `%s'"),
-						      buf,
-						      valid_table[cls1].name,
-						      valid_table[cls2].name));
+					  buf,
+					  valid_table[cls1].name,
+					  valid_table[cls2].name);
 			  }
 			break;
 
@@ -563,8 +557,8 @@  character '%s' in class `%s' must not be in class `%s'"),
 			break;
 
 		      default:
-			WITH_CUR_LOCALE (error (5, 0, _("\
-internal error in %s, line %u"), __FUNCTION__, __LINE__));
+			record_error (5, 0, _("\
+internal error in %s, line %u"), __FUNCTION__, __LINE__);
 		      }
 		  }
 	}
@@ -579,9 +573,8 @@  internal error in %s, line %u"), __FUNCTION__, __LINE__));
 	   (ELEM (ctype, class_collection, , space_value)
 	    & BITw (tok_blank)) == 0)))
     {
-      if (!be_quiet)
-	WITH_CUR_LOCALE (error (0, 0, _("<SP> character not in class `%s'"),
-				valid_table[cnt].name));
+      record_error (0, 0, _("<SP> character not in class `%s'"),
+		    valid_table[cnt].name);
     }
   else if (((cnt = BITPOS (tok_punct),
 	     (ELEM (ctype, class_collection, , space_value)
@@ -591,10 +584,9 @@  internal error in %s, line %u"), __FUNCTION__, __LINE__));
 		 & BITw (tok_graph))
 		!= 0)))
     {
-      if (!be_quiet)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+      record_error (0, 0, _("\
 <SP> character must not be in class `%s'"),
-				valid_table[cnt].name));
+				valid_table[cnt].name);
     }
   else
     ELEM (ctype, class_collection, , space_value) |= BITw (tok_print);
@@ -606,9 +598,8 @@  internal error in %s, line %u"), __FUNCTION__, __LINE__));
     space_seq = charmap_find_value (charmap, "U00000020", 9);
   if (space_seq == NULL || space_seq->nbytes != 1)
     {
-      if (!be_quiet)
-	WITH_CUR_LOCALE (error (0, 0, _("\
-character <SP> not defined in character map")));
+      record_error (0, 0, _("\
+character <SP> not defined in character map"));
     }
   else if (((cnt = BITPOS (tok_space),
 	     (ctype->class256_collection[space_seq->bytes[0]]
@@ -617,9 +608,8 @@  character <SP> not defined in character map")));
 		(ctype->class256_collection[space_seq->bytes[0]]
 		 & BIT (tok_blank)) == 0)))
     {
-      if (!be_quiet)
-	WITH_CUR_LOCALE (error (0, 0, _("<SP> character not in class `%s'"),
-				valid_table[cnt].name));
+       record_error (0, 0, _("<SP> character not in class `%s'"),
+		     valid_table[cnt].name);
     }
   else if (((cnt = BITPOS (tok_punct),
 	     (ctype->class256_collection[space_seq->bytes[0]]
@@ -628,10 +618,9 @@  character <SP> not defined in character map")));
 		(ctype->class256_collection[space_seq->bytes[0]]
 		 & BIT (tok_graph)) != 0)))
     {
-      if (!be_quiet)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+      record_error (0, 0, _("\
 <SP> character must not be in class `%s'"),
-				valid_table[cnt].name));
+		    valid_table[cnt].name);
     }
   else
     ctype->class256_collection[space_seq->bytes[0]] |= BIT (tok_print);
@@ -743,8 +732,8 @@  character <SP> not defined in character map")));
       assert (ctype->mbdigits_act == ctype->wcdigits_act);
       ctype->wcdigits_act -= ctype->mbdigits_act % 10;
       ctype->mbdigits_act -= ctype->mbdigits_act % 10;
-      WITH_CUR_LOCALE (error (0, 0, _("\
-`digit' category has not entries in groups of ten")));
+      record_error (0, 0, _("\
+`digit' category has not entries in groups of ten"));
     }
 
   /* Check the input digits.  There must be a multiple of ten available.
@@ -792,8 +781,8 @@  character <SP> not defined in character map")));
 	      if (ctype->mbdigits[cnt] == NULL)
 		{
 		  /* Hum, this ain't good.  */
-		  WITH_CUR_LOCALE (error (0, 0, _("\
-no input digits defined and none of the standard names in the charmap")));
+		  record_error (0, 0, _("\
+no input digits defined and none of the standard names in the charmap"));
 
 		  ctype->mbdigits[cnt] = obstack_alloc (&((struct charmap_t *) charmap)->mem_pool,
 							sizeof (struct charseq) + 1);
@@ -857,8 +846,8 @@  no input digits defined and none of the standard names in the charmap")));
 
 	if (!warned)
 	  {
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-not all characters used in `outdigit' are available in the charmap")));
+	    record_error (0, 0, _("\
+not all characters used in `outdigit' are available in the charmap"));
 	    warned = 1;
 	  }
 
@@ -874,8 +863,8 @@  not all characters used in `outdigit' are available in the charmap")));
       {
 	if (!warned)
 	  {
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-not all characters used in `outdigit' are available in the repertoire")));
+	    record_error (0, 0, _("\
+not all characters used in `outdigit' are available in the repertoire"));
 	    warned = 1;
 	  }
 
@@ -1145,9 +1134,9 @@  ctype_class_new (struct linereader *lr, struct locale_ctype_t *ctype,
 
   if (ctype->nr_charclass == MAX_NR_CHARCLASS)
     /* Exit code 2 is prescribed in P1003.2b.  */
-    WITH_CUR_LOCALE (error (2, 0, _("\
+    record_error (2, 0, _("\
 implementation limit: no more than %Zd character classes allowed"),
-			    MAX_NR_CHARCLASS));
+		  MAX_NR_CHARCLASS);
 
   ctype->classnames[ctype->nr_charclass++] = name;
 }
@@ -1177,9 +1166,9 @@  ctype_map_new (struct linereader *lr, struct locale_ctype_t *ctype,
 
   if (ctype->map_collection_nr == MAX_NR_CHARMAP)
     /* Exit code 2 is prescribed in P1003.2b.  */
-    WITH_CUR_LOCALE (error (2, 0, _("\
+    record_error (2, 0, _("\
 implementation limit: no more than %d character maps allowed"),
-			    MAX_NR_CHARMAP));
+		  MAX_NR_CHARMAP);
 
   ctype->mapnames[cnt] = name;
 
@@ -2743,11 +2732,11 @@  with character code range values one must use the absolute ellipsis `...'"));
 			    {
 			      lr_error (ldfile, _("\
 %s: duplicate `default_missing' definition"), "LC_CTYPE");
-			      WITH_CUR_LOCALE (error_at_line (0, 0,
-							      ctype->default_missing_file,
-							      ctype->default_missing_lineno,
-							      _("\
-previous definition was here")));
+			      record_error_at_line (0, 0,
+						    ctype->default_missing_file,
+						    ctype->default_missing_lineno,
+						    _("\
+previous definition was here"));
 			    }
 			  else
 			    {
@@ -2885,15 +2874,14 @@  set_one_default (struct locale_ctype_t *ctype,
         }
       if (seq == NULL)
         {
-          if (!be_quiet)
-            WITH_CUR_LOCALE (error (0, 0, _("\
+          record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-                                    "LC_CTYPE", tmp));
+			"LC_CTYPE", tmp);
         }
       else if (seq->nbytes != 1)
-        WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-                                "LC_CTYPE", tmp));
+		      "LC_CTYPE", tmp);
       else
         ctype->class256_collection[seq->bytes[0]] |= bit;
 
@@ -2982,15 +2970,14 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	seq = charmap_find_value (charmap, "U00000020", 9);
       if (seq == NULL)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-				    "LC_CTYPE", "<space>"));
+			"LC_CTYPE", "<space>");
 	}
       else if (seq->nbytes != 1)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-				"LC_CTYPE", "<space>"));
+		      "LC_CTYPE", "<space>");
       else
 	ctype->class256_collection[seq->bytes[0]] |= BIT (tok_space);
 
@@ -3002,15 +2989,14 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	seq = charmap_find_value (charmap, "U0000000C", 9);
       if (seq == NULL)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-				    "LC_CTYPE", "<form-feed>"));
+				    "LC_CTYPE", "<form-feed>");
 	}
       else if (seq->nbytes != 1)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-				"LC_CTYPE", "<form-feed>"));
+		      "LC_CTYPE", "<form-feed>");
       else
 	ctype->class256_collection[seq->bytes[0]] |= BIT (tok_space);
 
@@ -3023,15 +3009,14 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	seq = charmap_find_value (charmap, "U0000000A", 9);
       if (seq == NULL)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-				    "LC_CTYPE", "<newline>"));
+			"LC_CTYPE", "<newline>");
 	}
       else if (seq->nbytes != 1)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-				"LC_CTYPE", "<newline>"));
+		      "LC_CTYPE", "<newline>");
       else
 	ctype->class256_collection[seq->bytes[0]] |= BIT (tok_space);
 
@@ -3044,15 +3029,14 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	seq = charmap_find_value (charmap, "U0000000D", 9);
       if (seq == NULL)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-				    "LC_CTYPE", "<carriage-return>"));
+			"LC_CTYPE", "<carriage-return>");
 	}
       else if (seq->nbytes != 1)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-				"LC_CTYPE", "<carriage-return>"));
+		      "LC_CTYPE", "<carriage-return>");
       else
 	ctype->class256_collection[seq->bytes[0]] |= BIT (tok_space);
 
@@ -3065,15 +3049,14 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	seq = charmap_find_value (charmap, "U00000009", 9);
       if (seq == NULL)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-				    "LC_CTYPE", "<tab>"));
+			"LC_CTYPE", "<tab>");
 	}
       else if (seq->nbytes != 1)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-				"LC_CTYPE", "<tab>"));
+		      "LC_CTYPE", "<tab>");
       else
 	ctype->class256_collection[seq->bytes[0]] |= BIT (tok_space);
 
@@ -3086,15 +3069,14 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	seq = charmap_find_value (charmap, "U0000000B", 9);
       if (seq == NULL)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-				    "LC_CTYPE", "<vertical-tab>"));
+			"LC_CTYPE", "<vertical-tab>");
 	}
       else if (seq->nbytes != 1)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-				"LC_CTYPE", "<vertical-tab>"));
+		      "LC_CTYPE", "<vertical-tab>");
       else
 	ctype->class256_collection[seq->bytes[0]] |= BIT (tok_space);
 
@@ -3126,15 +3108,14 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	seq = charmap_find_value (charmap, "U00000020", 9);
       if (seq == NULL)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-				    "LC_CTYPE", "<space>"));
+			"LC_CTYPE", "<space>");
 	}
       else if (seq->nbytes != 1)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-				"LC_CTYPE", "<space>"));
+		      "LC_CTYPE", "<space>");
       else
 	ctype->class256_collection[seq->bytes[0]] |= BIT (tok_blank);
 
@@ -3147,15 +3128,14 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	seq = charmap_find_value (charmap, "U00000009", 9);
       if (seq == NULL)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	   record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-				    "LC_CTYPE", "<tab>"));
+		         "LC_CTYPE", "<tab>");
 	}
       else if (seq->nbytes != 1)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-				"LC_CTYPE", "<tab>"));
+		      "LC_CTYPE", "<tab>");
       else
 	ctype->class256_collection[seq->bytes[0]] |= BIT (tok_blank);
 
@@ -3212,15 +3192,14 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	seq = charmap_find_value (charmap, "U00000020", 9);
       if (seq == NULL)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-				    "LC_CTYPE", "<space>"));
+			"LC_CTYPE", "<space>");
 	}
       else if (seq->nbytes != 1)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: character `%s' in charmap not representable with one byte"),
-				"LC_CTYPE", "<space>"));
+		      "LC_CTYPE", "<space>");
       else
 	ctype->class256_collection[seq->bytes[0]] |= BIT (tok_print);
 
@@ -3254,17 +3233,15 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	    }
 	  if (seq_from == NULL)
 	    {
-	      if (!be_quiet)
-		WITH_CUR_LOCALE (error (0, 0, _("\
+	      record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-					"LC_CTYPE", tmp));
+			    "LC_CTYPE", tmp);
 	    }
 	  else if (seq_from->nbytes != 1)
 	    {
-	      if (!be_quiet)
-		WITH_CUR_LOCALE (error (0, 0, _("\
+	      record_error (0, 0, _("\
 %s: character `%s' needed as default value not representable with one byte"),
-					"LC_CTYPE", tmp));
+			    "LC_CTYPE", tmp);
 	    }
 	  else
 	    {
@@ -3279,17 +3256,15 @@  set_class_defaults (struct locale_ctype_t *ctype,
 		}
 	      if (seq_to == NULL)
 		{
-		  if (!be_quiet)
-		    WITH_CUR_LOCALE (error (0, 0, _("\
+		  record_error (0, 0, _("\
 %s: character `%s' not defined while needed as default value"),
-					    "LC_CTYPE", tmp));
+				"LC_CTYPE", tmp);
 		}
 	      else if (seq_to->nbytes != 1)
 		{
-		  if (!be_quiet)
-		    WITH_CUR_LOCALE (error (0, 0, _("\
+		  record_error (0, 0, _("\
 %s: character `%s' needed as default value not representable with one byte"),
-					    "LC_CTYPE", tmp));
+				"LC_CTYPE", tmp);
 		}
 	      else
 		/* The index [0] is determined by the order of the
@@ -3321,9 +3296,9 @@  set_class_defaults (struct locale_ctype_t *ctype,
   if (ctype->outdigits_act != 10)
     {
       if (ctype->outdigits_act != 0)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+	record_error (0, 0, _("\
 %s: field `%s' does not contain exactly ten entries"),
-				"LC_CTYPE", "outdigit"));
+		      "LC_CTYPE", "outdigit");
 
       for (size_t cnt = ctype->outdigits_act; cnt < 10; ++cnt)
 	{
@@ -3343,8 +3318,8 @@  set_class_defaults (struct locale_ctype_t *ctype,
 	  if (ctype->mboutdigits[cnt] == NULL)
 	    {
 	      /* Provide a replacement.  */
-	      WITH_CUR_LOCALE (error (0, 0, _("\
-no output digits defined and none of the standard names in the charmap")));
+	      record_error (0, 0, _("\
+no output digits defined and none of the standard names in the charmap"));
 
 	      ctype->mboutdigits[cnt] = obstack_alloc (&((struct charmap_t *) charmap)->mem_pool,
 						       sizeof (struct charseq)
@@ -3592,9 +3567,9 @@  translit_flatten (struct locale_ctype_t *ctype,
 
       if (other == NULL || other->categories[LC_CTYPE].ctype == NULL)
 	{
-	  WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: transliteration data from locale `%s' not available"),
-				  "LC_CTYPE", copy_locale));
+			"LC_CTYPE", copy_locale);
 	}
       else
 	{
@@ -3691,11 +3666,10 @@  allocate_arrays (struct locale_ctype_t *ctype, const struct charmap_t *charmap,
 	if (ctype->class_collection[idx] & _ISwbit (nr))
 	  wctype_table_add (t, ctype->charnames[idx]);
 
-      if (verbose)
-	WITH_CUR_LOCALE (fprintf (stderr, _("\
+      record_verbose (stderr, _("\
 %s: table for class \"%s\": %lu bytes\n"),
-				 "LC_CTYPE", ctype->classnames[nr],
-				 (unsigned long int) t->result_size));
+		      "LC_CTYPE", ctype->classnames[nr],
+		      (unsigned long int) t->result_size);
     }
 
   /* Room for table of mappings.  */
@@ -3756,11 +3730,10 @@  allocate_arrays (struct locale_ctype_t *ctype, const struct charmap_t *charmap,
 	  wctrans_table_add (t, ctype->charnames[idx],
 			     ctype->map_collection[nr][idx]);
 
-      if (verbose)
-	WITH_CUR_LOCALE (fprintf (stderr, _("\
+      record_verbose (stderr, _("\
 %s: table for map \"%s\": %lu bytes\n"),
-				 "LC_CTYPE", ctype->mapnames[nr],
-				 (unsigned long int) t->result_size));
+		      "LC_CTYPE", ctype->mapnames[nr],
+		      (unsigned long int) t->result_size);
     }
 
   /* Extra array for class and map names.  */
@@ -3881,9 +3854,8 @@  allocate_arrays (struct locale_ctype_t *ctype, const struct charmap_t *charmap,
     /* Set the width of L'\0' to 0.  */
     wcwidth_table_add (t, 0, 0);
 
-    if (verbose)
-      WITH_CUR_LOCALE (fprintf (stderr, _("%s: table for width: %lu bytes\n"),
-			       "LC_CTYPE", (unsigned long int) t->result_size));
+    record_verbose (stderr, _("%s: table for width: %lu bytes\n"),
+		    "LC_CTYPE", (unsigned long int) t->result_size);
   }
 
   /* Set MB_CUR_MAX.  */
diff --git a/locale/programs/ld-identification.c b/locale/programs/ld-identification.c
index 3e3ea64..70087e7 100644
--- a/locale/programs/ld-identification.c
+++ b/locale/programs/ld-identification.c
@@ -19,7 +19,6 @@ 
 # include <config.h>
 #endif
 
-#include <error.h>
 #include <langinfo.h>
 #include <stdlib.h>
 #include <string.h>
@@ -129,9 +128,8 @@  identification_finish (struct localedef_t *locale,
 	 empty one.  */
       if (identification == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_IDENTIFICATION"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_IDENTIFICATION");
 	  identification_startup (NULL, locale, 0);
 	  identification
 	    = locale->categories[LC_IDENTIFICATION].identification;
@@ -143,8 +141,8 @@  No definition for %s category found"), "LC_IDENTIFICATION"));
   if (identification->cat == NULL)					      \
     {									      \
       if (verbose && ! nothing)						      \
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),	      \
-				"LC_IDENTIFICATION", #cat));		      \
+	record_warning (_("%s: field `%s' not defined"), "LC_IDENTIFICATION", \
+			#cat);						      \
       identification->cat = "";						      \
     }
 
@@ -172,9 +170,9 @@  No definition for %s category found"), "LC_IDENTIFICATION"));
       if (identification->category[num] == NULL)
 	{
 	  if (verbose && ! nothing)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-%s: no identification for category `%s'"),
-				    "LC_IDENTIFICATION", category_name[num]));
+	    record_warning (_("\
+%s: no identification for category `%s'"), "LC_IDENTIFICATION",
+			    category_name[num]);
 	  identification->category[num] = "";
 	}
       else
@@ -196,11 +194,11 @@  No definition for %s category found"), "LC_IDENTIFICATION"));
 	      matched = true;
 
 	  if (matched != true)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	    record_error (0, 0, _("\
 %s: unknown standard `%s' for category `%s'"),
-				    "LC_IDENTIFICATION",
-				    identification->category[num],
-				    category_name[num]));
+			  "LC_IDENTIFICATION",
+			  identification->category[num],
+			  category_name[num]);
 	}
     }
 }
diff --git a/locale/programs/ld-measurement.c b/locale/programs/ld-measurement.c
index 92c849e..7057739 100644
--- a/locale/programs/ld-measurement.c
+++ b/locale/programs/ld-measurement.c
@@ -19,7 +19,6 @@ 
 # include <config.h>
 #endif
 
-#include <error.h>
 #include <langinfo.h>
 #include <string.h>
 #include <stdint.h>
@@ -90,9 +89,8 @@  measurement_finish (struct localedef_t *locale,
 	 empty one.  */
       if (measurement == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_MEASUREMENT"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_MEASUREMENT");
 	  measurement_startup (NULL, locale, 0);
 	  measurement = locale->categories[LC_MEASUREMENT].measurement;
 	  nothing = 1;
@@ -102,16 +100,16 @@  No definition for %s category found"), "LC_MEASUREMENT"));
   if (measurement->measurement == 0)
     {
       if (! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_MEASUREMENT", "measurement"));
+	record_error (0, 0, _("%s: field `%s' not defined"),
+		      "LC_MEASUREMENT", "measurement");
       /* Use as the default value the value of the i18n locale.  */
       measurement->measurement = 1;
     }
   else
     {
       if (measurement->measurement > 3)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: invalid value for field `%s'"),
-				"LC_MEASUREMENT", "measurement"));
+	record_error (0, 0, _("%s: invalid value for field `%s'"),
+		      "LC_MEASUREMENT", "measurement");
     }
 }
 
diff --git a/locale/programs/ld-messages.c b/locale/programs/ld-messages.c
index bc86ec0..e44ec57 100644
--- a/locale/programs/ld-messages.c
+++ b/locale/programs/ld-messages.c
@@ -93,9 +93,8 @@  messages_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 	 empty one.  */
       if (messages == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_MESSAGES"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_MESSAGES");
 	  messages_startup (NULL, locale, 0);
 	  messages = locale->categories[LC_MESSAGES].messages;
 	  nothing = 1;
@@ -110,17 +109,16 @@  No definition for %s category found"), "LC_MESSAGES"));
 
   if (messages->yesexpr == NULL)
     {
-      if (! be_quiet && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' undefined"),
-				"LC_MESSAGES", "yesexpr"));
+      if (! nothing)
+	record_error (0, 0, _("%s: field `%s' undefined"),
+		      "LC_MESSAGES", "yesexpr");
       messages->yesexpr = "^[yY]";
     }
   else if (messages->yesexpr[0] == '\0')
     {
-      if (!be_quiet)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+      record_error (0, 0, _("\
 %s: value for field `%s' must not be an empty string"),
-				"LC_MESSAGES", "yesexpr"));
+		    "LC_MESSAGES", "yesexpr");
     }
   else
     {
@@ -134,9 +132,9 @@  No definition for %s category found"), "LC_MESSAGES"));
 	  char errbuf[BUFSIZ];
 
 	  (void) regerror (result, &re, errbuf, BUFSIZ);
-	  WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: no correct regular expression for field `%s': %s"),
-				  "LC_MESSAGES", "yesexpr", errbuf));
+			"LC_MESSAGES", "yesexpr", errbuf);
 	}
       else if (result != 0)
 	regfree (&re);
@@ -144,17 +142,16 @@  No definition for %s category found"), "LC_MESSAGES"));
 
   if (messages->noexpr == NULL)
     {
-      if (! be_quiet && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' undefined"),
-				"LC_MESSAGES", "noexpr"));
+      if (! nothing)
+	record_error (0, 0, _("%s: field `%s' undefined"),
+		      "LC_MESSAGES", "noexpr");
       messages->noexpr = "^[nN]";
     }
   else if (messages->noexpr[0] == '\0')
     {
-      if (!be_quiet)
-	WITH_CUR_LOCALE (error (0, 0, _("\
+      record_error (0, 0, _("\
 %s: value for field `%s' must not be an empty string"),
-				"LC_MESSAGES", "noexpr"));
+		    "LC_MESSAGES", "noexpr");
     }
   else
     {
@@ -168,9 +165,9 @@  No definition for %s category found"), "LC_MESSAGES"));
 	  char errbuf[BUFSIZ];
 
 	  (void) regerror (result, &re, errbuf, BUFSIZ);
-	  WITH_CUR_LOCALE (error (0, 0, _("\
+	  record_error (0, 0, _("\
 %s: no correct regular expression for field `%s': %s"),
-				  "LC_MESSAGES", "noexpr", errbuf));
+			"LC_MESSAGES", "noexpr", errbuf);
 	}
       else if (result != 0)
 	regfree (&re);
diff --git a/locale/programs/ld-monetary.c b/locale/programs/ld-monetary.c
index cd50541..a0befe5 100644
--- a/locale/programs/ld-monetary.c
+++ b/locale/programs/ld-monetary.c
@@ -189,9 +189,8 @@  monetary_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 	 empty one.  */
       if (monetary == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_MONETARY"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_MONETARY");
 	  monetary_startup (NULL, locale, 0);
 	  monetary = locale->categories[LC_MONETARY].monetary;
 	  nothing = 1;
@@ -201,9 +200,9 @@  No definition for %s category found"), "LC_MONETARY"));
 #define TEST_ELEM(cat, initval) \
   if (monetary->cat == NULL)						      \
     {									      \
-      if (! be_quiet && ! nothing)					      \
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),	      \
-				"LC_MONETARY", #cat));			      \
+      if (! nothing)							      \
+	record_error (0, 0, _("%s: field `%s' not defined"),		      \
+		      "LC_MONETARY", #cat);				      \
       monetary->cat = initval;						      \
     }
 
@@ -219,10 +218,10 @@  No definition for %s category found"), "LC_MONETARY"));
     {
       if (strlen (monetary->int_curr_symbol) != 4)
 	{
-	  if (! be_quiet && ! nothing)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+	  if (! nothing)
+	    record_error (0, 0, _("\
 %s: value of field `int_curr_symbol' has wrong length"),
-				    "LC_MONETARY"));
+			  "LC_MONETARY");
 	}
       else
 	{ /* Check the first three characters against ISO 4217 */
@@ -231,12 +230,11 @@  No definition for %s category found"), "LC_MONETARY"));
 	  symbol[3] = '\0';
 	  if (bsearch (symbol, valid_int_curr, NR_VALID_INT_CURR,
 		       sizeof (const char *),
-		       (comparison_fn_t) curr_strcmp) == NULL
-	       && !be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
+		       (comparison_fn_t) curr_strcmp) == NULL)
+	    record_error (0, 0, _("\
 %s: value of field `int_curr_symbol' does \
 not correspond to a valid name in ISO 4217"),
-				"LC_MONETARY"));
+			  "LC_MONETARY");
 	}
     }
 
@@ -245,25 +243,25 @@  not correspond to a valid name in ISO 4217"),
      != "".  */
   if (monetary->mon_decimal_point == NULL)
     {
-      if (! be_quiet && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_MONETARY", "mon_decimal_point"));
+      if (! nothing)
+	record_error (0, 0, _("%s: field `%s' not defined"),
+		      "LC_MONETARY", "mon_decimal_point");
       monetary->mon_decimal_point = ".";
     }
   else if (monetary->mon_decimal_point[0] == '\0' && ! be_quiet && ! nothing)
     {
-      WITH_CUR_LOCALE (error (0, 0, _("\
+      record_error (0, 0, _("\
 %s: value for field `%s' must not be an empty string"),
-			      "LC_MONETARY", "mon_decimal_point"));
+		    "LC_MONETARY", "mon_decimal_point");
     }
   if (monetary->mon_decimal_point_wc == L'\0')
     monetary->mon_decimal_point_wc = L'.';
 
   if (monetary->mon_grouping_len == 0)
     {
-      if (! be_quiet && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_MONETARY", "mon_grouping"));
+      if (! nothing)
+	record_error (0, 0, _("%s: field `%s' not defined"),
+		      "LC_MONETARY", "mon_grouping");
 
       monetary->mon_grouping = (char *) "\177";
       monetary->mon_grouping_len = 1;
@@ -273,17 +271,17 @@  not correspond to a valid name in ISO 4217"),
 #define TEST_ELEM(cat, min, max, initval) \
   if (monetary->cat == -2)						      \
     {									      \
-       if (! be_quiet && ! nothing)					      \
-	 WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),	      \
-				 "LC_MONETARY", #cat));			      \
+       if (! nothing)							      \
+	 record_error (0, 0, _("%s: field `%s' not defined"),		      \
+		       "LC_MONETARY", #cat);				      \
        monetary->cat = initval;						      \
     }									      \
   else if ((monetary->cat < min || monetary->cat > max)			      \
 	   && min < max							      \
 	   && !be_quiet && !nothing)					      \
-    WITH_CUR_LOCALE (error (0, 0, _("\
+    record_error (0, 0, _("\
 %s: value for field `%s' must be in range %d...%d"),			      \
-			    "LC_MONETARY", #cat, min, max))
+		  "LC_MONETARY", #cat, min, max)
 
   TEST_ELEM (int_frac_digits, 1, 0, -1);
   TEST_ELEM (frac_digits, 1, 0, -1);
@@ -309,11 +307,10 @@  not correspond to a valid name in ISO 4217"),
 #define TEST_ELEM(cat, alt, min, max) \
   if (monetary->cat == -2)						      \
     monetary->cat = monetary->alt;					      \
-  else if ((monetary->cat < min || monetary->cat > max) && !be_quiet	      \
-	   && ! nothing)						      \
-    WITH_CUR_LOCALE (error (0, 0, _("\
+  else if ((monetary->cat < min || monetary->cat > max)	&& ! nothing)	      \
+    record_error (0, 0, _("\
 %s: value for field `%s' must be in range %d...%d"),			      \
-			    "LC_MONETARY", #cat, min, max))
+		  "LC_MONETARY", #cat, min, max)
 
   TEST_ELEM (int_p_cs_precedes, p_cs_precedes, -1, 1);
   TEST_ELEM (int_p_sep_by_space, p_sep_by_space, -1, 2);
diff --git a/locale/programs/ld-name.c b/locale/programs/ld-name.c
index ee50ae7..542ae4f 100644
--- a/locale/programs/ld-name.c
+++ b/locale/programs/ld-name.c
@@ -90,9 +90,8 @@  name_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 	 empty one.  */
       if (name == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_NAME"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_NAME");
 	  name_startup (NULL, locale, 0);
 	  name = locale->categories[LC_NAME].name;
 	  nothing = 1;
@@ -102,8 +101,8 @@  No definition for %s category found"), "LC_NAME"));
   if (name->name_fmt == NULL)
     {
       if (! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_NAME", "name_fmt"));
+	record_error (0, 0, _("%s: field `%s' not defined"),
+		      "LC_NAME", "name_fmt");
       /* Use as the default value the value of the i18n locale.  */
       name->name_fmt = "%p%t%g%t%m%t%f";
     }
@@ -114,8 +113,8 @@  No definition for %s category found"), "LC_NAME"));
       const char *cp = name->name_fmt;
 
       if (*cp == '\0')
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' must not be empty"),
-				"LC_NAME", "name_fmt"));
+	record_error (0, 0, _("%s: field `%s' must not be empty"),
+		      "LC_NAME", "name_fmt");
       else
 	while (*cp != '\0')
 	  {
@@ -126,8 +125,8 @@  No definition for %s category found"), "LC_NAME"));
 		  ++cp;
 		if (strchr ("dfFgGlomMpsSt", *cp) == NULL)
 		  {
-		    WITH_CUR_LOCALE (error (0, 0, _("\
-%s: invalid escape sequence in field `%s'"), "LC_NAME", "name_fmt"));
+		    record_error (0, 0, _("\
+%s: invalid escape sequence in field `%s'"), "LC_NAME", "name_fmt");
 		    break;
 		  }
 	      }
@@ -139,8 +138,7 @@  No definition for %s category found"), "LC_NAME"));
   if (name->cat == NULL)						      \
     {									      \
       if (verbose && ! nothing)						      \
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),	      \
-				"LC_NAME", #cat));          		      \
+	record_warning (_("%s: field `%s' not defined"), "LC_NAME", #cat);    \
       name->cat = "";							      \
     }
 
diff --git a/locale/programs/ld-numeric.c b/locale/programs/ld-numeric.c
index a81ff04..8c04ed7 100644
--- a/locale/programs/ld-numeric.c
+++ b/locale/programs/ld-numeric.c
@@ -94,9 +94,8 @@  numeric_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 	 empty one.  */
       if (numeric == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_NUMERIC"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_NUMERIC");
 	  numeric_startup (NULL, locale, 0);
 	  numeric = locale->categories[LC_NUMERIC].numeric;
 	  nothing = 1;
@@ -108,23 +107,23 @@  No definition for %s category found"), "LC_NUMERIC"));
      != "".  */
   if (numeric->decimal_point == NULL)
     {
-      if (! be_quiet && ! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_NUMERIC", "decimal_point"));
+      if (! nothing)
+	record_error (0, 0, _("%s: field `%s' not defined"),
+		      "LC_NUMERIC", "decimal_point");
       numeric->decimal_point = ".";
     }
-  else if (numeric->decimal_point[0] == '\0' && ! be_quiet && ! nothing)
+  else if (numeric->decimal_point[0] == '\0' && ! nothing)
     {
-      WITH_CUR_LOCALE (error (0, 0, _("\
+      record_error (0, 0, _("\
 %s: value for field `%s' must not be an empty string"),
-			      "LC_NUMERIC", "decimal_point"));
+		    "LC_NUMERIC", "decimal_point");
     }
   if (numeric->decimal_point_wc == L'\0')
     numeric->decimal_point_wc = L'.';
 
-  if (numeric->grouping_len == 0 && ! be_quiet && ! nothing)
-    WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-			    "LC_NUMERIC", "grouping"));
+  if (numeric->grouping_len == 0 && ! nothing)
+    record_error (0, 0, _("%s: field `%s' not defined"),
+		  "LC_NUMERIC", "grouping");
 }
 
 
diff --git a/locale/programs/ld-paper.c b/locale/programs/ld-paper.c
index df7ce12..7bde11e 100644
--- a/locale/programs/ld-paper.c
+++ b/locale/programs/ld-paper.c
@@ -19,7 +19,6 @@ 
 # include <config.h>
 #endif
 
-#include <error.h>
 #include <langinfo.h>
 #include <string.h>
 #include <stdint.h>
@@ -87,9 +86,8 @@  paper_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 	 empty one.  */
       if (paper == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_PAPER"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_PAPER");
 	  paper_startup (NULL, locale, 0);
 	  paper = locale->categories[LC_PAPER].paper;
 	  nothing = 1;
@@ -99,8 +97,8 @@  No definition for %s category found"), "LC_PAPER"));
   if (paper->height == 0)
     {
       if (! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_PAPER", "height"));
+	record_error (0, 0, _("%s: field `%s' not defined"),
+		      "LC_PAPER", "height");
       /* Use as default values the values from the i18n locale.  */
       paper->height = 297;
     }
@@ -108,8 +106,8 @@  No definition for %s category found"), "LC_PAPER"));
   if (paper->width == 0)
     {
       if (! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_PAPER", "width"));
+	record_error (0, 0, _("%s: field `%s' not defined"),
+		      "LC_PAPER", "width");
       /* Use as default values the values from the i18n locale.  */
       paper->width = 210;
     }
diff --git a/locale/programs/ld-telephone.c b/locale/programs/ld-telephone.c
index b62280a..3b0c84c 100644
--- a/locale/programs/ld-telephone.c
+++ b/locale/programs/ld-telephone.c
@@ -19,7 +19,6 @@ 
 # include <config.h>
 #endif
 
-#include <error.h>
 #include <langinfo.h>
 #include <string.h>
 #include <stdint.h>
@@ -90,9 +89,8 @@  telephone_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 	 empty one.  */
       if (telephone == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_TELEPHONE"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_TELEPHONE");
 	  telephone_startup (NULL, locale, 0);
 	  telephone = locale->categories[LC_TELEPHONE].telephone;
 	  nothing = 1;
@@ -102,8 +100,8 @@  No definition for %s category found"), "LC_TELEPHONE"));
   if (telephone->tel_int_fmt == NULL)
     {
       if (! nothing)
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),
-				"LC_TELEPHONE", "tel_int_fmt"));
+	record_error (0, 0, _("%s: field `%s' not defined"),
+		      "LC_TELEPHONE", "tel_int_fmt");
       /* Use as the default value the value of the i18n locale.  */
       telephone->tel_int_fmt = "+%c %a%t%l";
     }
@@ -114,8 +112,8 @@  No definition for %s category found"), "LC_TELEPHONE"));
       const char *cp = telephone->tel_int_fmt;
 
       if (*cp == '\0')
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' must not be empty"),
-				"LC_TELEPHONE", "tel_int_fmt"));
+	record_error (0, 0, _("%s: field `%s' must not be empty"),
+		      "LC_TELEPHONE", "tel_int_fmt");
       else
 	while (*cp != '\0')
 	  {
@@ -123,8 +121,8 @@  No definition for %s category found"), "LC_TELEPHONE"));
 	      {
 		if (strchr ("aAcCelt", *++cp) == NULL)
 		  {
-		    WITH_CUR_LOCALE (error (0, 0, _("\
-%s: invalid escape sequence in field `%s'"), "LC_TELEPHONE", "tel_int_fmt"));
+		    record_error (0, 0, _("\
+%s: invalid escape sequence in field `%s'"), "LC_TELEPHONE", "tel_int_fmt");
 		    break;
 		  }
 	      }
@@ -146,8 +144,8 @@  No definition for %s category found"), "LC_TELEPHONE"));
 	    {
 	      if (strchr ("aAcCelt", *++cp) == NULL)
 		{
-		  WITH_CUR_LOCALE (error (0, 0, _("\
-%s: invalid escape sequence in field `%s'"), "LC_TELEPHONE", "tel_dom_fmt"));
+		  record_error (0, 0, _("\
+%s: invalid escape sequence in field `%s'"), "LC_TELEPHONE", "tel_dom_fmt");
 		  break;
 		}
 	    }
@@ -159,8 +157,8 @@  No definition for %s category found"), "LC_TELEPHONE"));
   if (telephone->cat == NULL)						      \
     {									      \
       if (verbose && ! nothing)						      \
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),	      \
-				"LC_TELEPHONE", #cat));     		      \
+	record_warning (_("%s: field `%s' not defined"), "LC_TELEPHONE",      \
+			#cat);						      \
       telephone->cat = "";						      \
     }
 
diff --git a/locale/programs/ld-time.c b/locale/programs/ld-time.c
index 32e9c41..e60dc9e 100644
--- a/locale/programs/ld-time.c
+++ b/locale/programs/ld-time.c
@@ -155,9 +155,8 @@  time_finish (struct localedef_t *locale, const struct charmap_t *charmap)
 	 empty one.  */
       if (time == NULL)
 	{
-	  if (! be_quiet)
-	    WITH_CUR_LOCALE (error (0, 0, _("\
-No definition for %s category found"), "LC_TIME"));
+	  record_error (0, 0, _("\
+No definition for %s category found"), "LC_TIME");
 	  time_startup (NULL, locale, 0);
 	  time = locale->categories[LC_TIME].time;
 	  nothing = 1;
@@ -171,9 +170,9 @@  No definition for %s category found"), "LC_TIME"));
       const char *initval[] = { noparen val };				      \
       unsigned int i;							      \
 									      \
-      if (! be_quiet && ! nothing)					      \
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),	      \
-				"LC_TIME", #cat));          		      \
+      if (! nothing)					    		      \
+	record_error (0, 0, _("%s: field `%s' not defined"),	      	      \
+		      "LC_TIME", #cat);          			      \
 									      \
       for (i = 0; i < sizeof (initval) / sizeof (initval[0]); ++i)	      \
 	time->cat[i] = initval[i];					      \
@@ -192,9 +191,9 @@  No definition for %s category found"), "LC_TIME"));
 #define TEST_ELEM(cat, initval) \
   if (time->cat == NULL)						      \
     {									      \
-      if (! be_quiet && ! nothing)					      \
-	WITH_CUR_LOCALE (error (0, 0, _("%s: field `%s' not defined"),	      \
-				"LC_TIME", #cat));          		      \
+      if (! nothing)							      \
+	record_error (0, 0, _("%s: field `%s' not defined"),		      \
+		      "LC_TIME", #cat);          			      \
 									      \
       time->cat = initval;						      \
     }
@@ -243,10 +242,9 @@  No definition for %s category found"), "LC_TIME"));
 	  /* First character must be + or - for the direction.  */
 	  if (*str != '+' && *str != '-')
 	    {
-	      if (!be_quiet)
-		WITH_CUR_LOCALE (error (0, 0, _("\
+	      record_error (0, 0, _("\
 %s: direction flag in string %Zd in `era' field is not '+' nor '-'"),
-					"LC_TIME", idx + 1));
+			    "LC_TIME", idx + 1);
 	      /* Default arbitrarily to '+'.  */
 	      time->era_entries[idx].direction = '+';
 	    }
@@ -254,10 +252,9 @@  No definition for %s category found"), "LC_TIME"));
 	    time->era_entries[idx].direction = *str;
 	  if (*++str != ':')
 	    {
-	      if (!be_quiet)
-		WITH_CUR_LOCALE (error (0, 0, _("\
+	      record_error (0, 0, _("\
 %s: direction flag in string %Zd in `era' field is not a single character"),
-					"LC_TIME", idx + 1));
+			    "LC_TIME", idx + 1);
 	      (void) strsep (&str, ":");
 	    }
 	  else
@@ -267,18 +264,16 @@  No definition for %s category found"), "LC_TIME"));
 	  time->era_entries[idx].offset = strtol (str, &endp, 10);
 	  if (endp == str)
 	    {
-	      if (!be_quiet)
-		WITH_CUR_LOCALE (error (0, 0, _("\
+	      record_error (0, 0, _("\
 %s: invalid number for offset in string %Zd in `era' field"),
-					"LC_TIME", idx + 1));
+			    "LC_TIME", idx + 1);
 	      (void) strsep (&str, ":");
 	    }
 	  else if (*endp != ':')
 	    {
-	      if (!be_quiet)
-		WITH_CUR_LOCALE (error (0, 0, _("\
+	      record_error (0, 0, _("\
 %s: garbage at end of offset value in string %Zd in `era' field"),
-					"LC_TIME", idx + 1));
+			    "LC_TIME", idx + 1);
 	      (void) strsep (&str, ":");
 	    }
 	  else
@@ -326,19 +321,17 @@  No definition for %s category found"), "LC_TIME"));
 	      if (endp == str)
 		{
 		invalid_start_date:
-		  if (!be_quiet)
-		    WITH_CUR_LOCALE (error (0, 0, _("\
+		  record_error (0, 0, _("\
 %s: invalid starting date in string %Zd in `era' field"),
-					    "LC_TIME", idx + 1));
+				"LC_TIME", idx + 1);
 		  (void) strsep (&str, ":");
 		}
 	      else if (*endp != ':')
 		{
 		garbage_start_date:
-		  if (!be_quiet)
-		    WITH_CUR_LOCALE (error (0, 0, _("\
+		  record_error (0, 0, _("\
 %s: garbage at end of starting date in string %Zd in `era' field "),
-					    "LC_TIME", idx + 1));
+				"LC_TIME", idx + 1);
 		  (void) strsep (&str, ":");
 		}
 	      else
@@ -353,11 +346,10 @@  No definition for %s category found"), "LC_TIME"));
 			   > days_per_month[time->era_entries[idx].start_date[1]])
 		       || (time->era_entries[idx].start_date[1] == 2
 			   && time->era_entries[idx].start_date[2] == 29
-			   && !__isleap (time->era_entries[idx].start_date[0])))
-		      && !be_quiet)
-			  WITH_CUR_LOCALE (error (0, 0, _("\
+			   && !__isleap (time->era_entries[idx].start_date[0]))))
+		    record_error (0, 0, _("\
 %s: starting date is invalid in string %Zd in `era' field"),
-						  "LC_TIME", idx + 1));
+				  "LC_TIME", idx + 1);
 		}
 	    }
 
@@ -403,19 +395,17 @@  No definition for %s category found"), "LC_TIME"));
 	      if (endp == str)
 		{
 		invalid_stop_date:
-		  if (!be_quiet)
-		    WITH_CUR_LOCALE (error (0, 0, _("\
+		  record_error (0, 0, _("\
 %s: invalid stopping date in string %Zd in `era' field"),
-					    "LC_TIME", idx + 1));
+				"LC_TIME", idx + 1);
 		  (void) strsep (&str, ":");
 		}
 	      else if (*endp != ':')
 		{
 		garbage_stop_date:
-		  if (!be_quiet)
-		    WITH_CUR_LOCALE (error (0, 0, _("\
+		  record_error (0, 0, _("\
 %s: garbage at end of stopping date in string %Zd in `era' field"),
-					    "LC_TIME", idx + 1));
+				"LC_TIME", idx + 1);
 		  (void) strsep (&str, ":");
 		}
 	      else
@@ -430,19 +420,17 @@  No definition for %s category found"), "LC_TIME"));
 			   > days_per_month[time->era_entries[idx].stop_date[1]])
 		       || (time->era_entries[idx].stop_date[1] == 2
 			   && time->era_entries[idx].stop_date[2] == 29
-			   && !__isleap (time->era_entries[idx].stop_date[0])))
-		      && !be_quiet)
-			  WITH_CUR_LOCALE (error (0, 0, _("\
+			   && !__isleap (time->era_entries[idx].stop_date[0]))))
+		    record_error (0, 0, _("\
 %s: invalid stopping date in string %Zd in `era' field"),
-						  "LC_TIME", idx + 1));
+				  "LC_TIME", idx + 1);
 		}
 	    }
 
 	  if (str == NULL || *str == '\0')
 	    {
-	      if (!be_quiet)
-		WITH_CUR_LOCALE (error (0, 0, _("\
-%s: missing era name in string %Zd in `era' field"), "LC_TIME", idx + 1));
+	      record_error (0, 0, _("\
+%s: missing era name in string %Zd in `era' field"), "LC_TIME", idx + 1);
 	      time->era_entries[idx].name =
 		time->era_entries[idx].format = "";
 	    }
@@ -452,10 +440,9 @@  No definition for %s category found"), "LC_TIME"));
 
 	      if (str == NULL || *str == '\0')
 		{
-		  if (!be_quiet)
-		    WITH_CUR_LOCALE (error (0, 0, _("\
+		  record_error (0, 0, _("\
 %s: missing era format in string %Zd in `era' field"),
-					    "LC_TIME", idx + 1));
+				"LC_TIME", idx + 1);
 		  time->era_entries[idx].name =
 		    time->era_entries[idx].format = "";
 		}
@@ -498,33 +485,33 @@  No definition for %s category found"), "LC_TIME"));
     time->week_1stweek = 7;
 
   if (time->week_1stweek > time->week_ndays)
-    WITH_CUR_LOCALE (error (0, 0, _("\
+    record_error (0, 0, _("\
 %s: third operand for value of field `%s' must not be larger than %d"),
-			    "LC_TIME", "week", 7));
+		  "LC_TIME", "week", 7);
 
   if (time->first_weekday == '\0')
     /* The definition does not specify this so the default is used.  */
     time->first_weekday = 1;
   else if (time->first_weekday > time->week_ndays)
-    WITH_CUR_LOCALE (error (0, 0, _("\
+    record_error (0, 0, _("\
 %s: values for field `%s' must not be larger than %d"),
-			    "LC_TIME", "first_weekday", 7));
+		  "LC_TIME", "first_weekday", 7);
 
   if (time->first_workday == '\0')
     /* The definition does not specify this so the default is used.  */
     time->first_workday = 2;
   else if (time->first_workday > time->week_ndays)
-    WITH_CUR_LOCALE (error (0, 0, _("\
+    record_error (0, 0, _("\
 %s: values for field `%s' must not be larger than %d"),
-			    "LC_TIME", "first_workday", 7));
+		  "LC_TIME", "first_workday", 7);
 
   if (time->cal_direction == '\0')
     /* The definition does not specify this so the default is used.  */
     time->cal_direction = 1;
   else if (time->cal_direction > 3)
-    WITH_CUR_LOCALE (error (0, 0, _("\
+    record_error (0, 0, _("\
 %s: values for field `%s' must not be larger than %d"),
-			    "LC_TIME", "cal_direction", 3));
+		  "LC_TIME", "cal_direction", 3);
 
   /* XXX We don't perform any tests on the timezone value since this is
      simply useless, stupid $&$!@...  */
diff --git a/locale/programs/linereader.h b/locale/programs/linereader.h
index 3965db5..6f538a0 100644
--- a/locale/programs/linereader.h
+++ b/locale/programs/linereader.h
@@ -27,7 +27,7 @@ 
 #include "error.h"
 #include "locfile-token.h"
 #include "repertoire.h"
-
+#include "record-status.h"
 
 typedef const struct keyword_t *(*kw_hash_fct_t) (const char *, unsigned int);
 struct charset_t;
@@ -96,9 +96,29 @@  extern struct token *lr_token (struct linereader *lr,
 extern void lr_ignore_rest (struct linereader *lr, int verbose);
 
 
-#define lr_error(lr, fmt, args...) \
-  WITH_CUR_LOCALE (error_at_line (0, 0, lr->fname, lr->lineno, fmt, ## args))
+static inline void
+__attribute__ ((__format__ (__printf__, 2, 3), nonnull (1, 2)))
+lr_error (struct linereader *lr, const char *fmt, ...)
+{
+  char *str;
+  va_list arg;
+  struct locale_state ls;
+  int ret;
+
+  va_start (arg, fmt); 
+  ls = push_locale ();
+
+  ret = vasprintf (&str, fmt, arg);
+  if (ret == -1)
+    abort ();
 
+  pop_locale (ls);
+  va_end (arg); 
+
+  error_at_line (0, 0, lr->fname, lr->lineno, "%s", str);
+
+  free (str);
+}
 
 
 static inline int
diff --git a/locale/programs/localedef.c b/locale/programs/localedef.c
index 6acc134..7d76154 100644
--- a/locale/programs/localedef.c
+++ b/locale/programs/localedef.c
@@ -51,6 +51,12 @@  int posix_conformance;
 /* If not zero give a lot more messages.  */
 int verbose;
 
+/* Warnings recorded by record_warnings (see localedef.h).  */
+int recorded_warning_count;
+
+/* Errors recorded by record_error (see localedef.h).  */
+int recorded_error_count;
+
 /* If not zero suppress warnings and information messages.  */
 int be_quiet;
 
@@ -236,8 +242,8 @@  main (int argc, char *argv[])
      defines error code 3 for this situation so I think it must be
      a fatal error (see P1003.2 4.35.8).  */
   if (sysconf (_SC_2_LOCALEDEF) < 0)
-    WITH_CUR_LOCALE (error (3, 0, _("\
-FATAL: system does not define `_POSIX2_LOCALEDEF'")));
+    record_error (3, 0, _("\
+FATAL: system does not define `_POSIX2_LOCALEDEF'"));
 
   /* Process charmap file.  */
   charmap = charmap_read (charmap_file, verbose, 1, be_quiet, 1);
@@ -250,8 +256,8 @@  FATAL: system does not define `_POSIX2_LOCALEDEF'")));
 
   /* Now read the locale file.  */
   if (locfile_read (&global, charmap) != 0)
-    WITH_CUR_LOCALE (error (4, errno, _("\
-cannot open locale definition file `%s'"), input_file));
+    record_error (4, errno, _("\
+cannot open locale definition file `%s'"), input_file);
 
   /* Perhaps we saw some `copy' instructions.  */
   while (1)
@@ -266,29 +272,41 @@  cannot open locale definition file `%s'"), input_file));
 	break;
 
       if (locfile_read (runp, charmap) != 0)
-	WITH_CUR_LOCALE (error (4, errno, _("\
-cannot open locale definition file `%s'"), runp->name));
+	record_error (4, errno, _("\
+cannot open locale definition file `%s'"), runp->name);
     }
 
   /* Check the categories we processed in source form.  */
   check_all_categories (locales, charmap);
 
-  /* We are now able to write the data files.  If warning were given we
-     do it only if it is explicitly requested (--force).  */
-  if (error_message_count == 0 || force_output != 0)
+  /* What we do next depends on the number of errors and warnings we
+     have generated in processing the input files.
+
+     * No errors: Write the output file.
+
+     * Some warnings: Write the output file and exit with status 1 to
+     indicate there may be problems using the output file e.g. missing
+     data that makes it difficult to use
+
+     * Errors: We don't write the output file and we exit with status 4
+     to indicate no output files were written.
+
+     The use of -c|--force writes the output file even if errors were
+     seen.  */
+  if (recorded_error_count == 0 || force_output != 0)
     {
       if (cannot_write_why != 0)
-	WITH_CUR_LOCALE (error (4, cannot_write_why, _("\
-cannot write output files to `%s'"), output_path ? : argv[remaining]));
+	record_error (4, cannot_write_why, _("\
+cannot write output files to `%s'"), output_path ? : argv[remaining]);
       else
 	write_all_categories (locales, charmap, argv[remaining], output_path);
     }
   else
-    WITH_CUR_LOCALE (error (4, 0, _("\
-no output file produced because warnings were issued")));
+    record_error (4, 0, _("\
+no output file produced because errors were issued"));
 
   /* This exit status is prescribed by POSIX.2 4.35.7.  */
-  exit (error_message_count != 0);
+  exit (recorded_warning_count != 0);
 }
 
 
@@ -567,14 +585,14 @@  add_to_readlist (int category, const char *name, const char *repertoire_name,
   if (generate
       && (runp->needed & (1 << category)) != 0
       && (runp->avail & (1 << category)) == 0)
-    WITH_CUR_LOCALE (error (5, 0, _("\
-circular dependencies between locale definitions")));
+    record_error (5, 0, _("\
+circular dependencies between locale definitions"));
 
   if (copy_locale != NULL)
     {
       if (runp->categories[category].generic != NULL)
-	WITH_CUR_LOCALE (error (5, 0, _("\
-cannot add already read locale `%s' a second time"), name));
+	record_error (5, 0, _("\
+cannot add already read locale `%s' a second time"), name);
       else
 	runp->categories[category].generic =
 	  copy_locale->categories[category].generic;
@@ -599,8 +617,8 @@  find_locale (int category, const char *name, const char *repertoire_name,
 
   if ((result->avail & (1 << category)) == 0
       && locfile_read (result, charmap) != 0)
-    WITH_CUR_LOCALE (error (4, errno, _("\
-cannot open locale definition file `%s'"), result->name));
+    record_error (4, errno, _("\
+cannot open locale definition file `%s'"), result->name);
 
   return result;
 }
@@ -619,8 +637,8 @@  load_locale (int category, const char *name, const char *repertoire_name,
 
   if ((result->avail & (1 << category)) == 0
       && locfile_read (result, charmap) != 0)
-    WITH_CUR_LOCALE (error (4, errno, _("\
-cannot open locale definition file `%s'"), result->name));
+    record_error (4, errno, _("\
+cannot open locale definition file `%s'"), result->name);
 
   return result;
 }
diff --git a/locale/programs/localedef.h b/locale/programs/localedef.h
index 74a2eba..96aa696 100644
--- a/locale/programs/localedef.h
+++ b/locale/programs/localedef.h
@@ -24,7 +24,11 @@ 
 #include <locale.h>
 #include <stdbool.h>
 #include <stddef.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
 
+#include "record-status.h"
 #include "repertoire.h"
 #include "../locarchive.h"
 
@@ -111,7 +115,6 @@  struct localedef_t
 
 /* Global variables of the localedef program.  */
 extern int verbose;
-extern int be_quiet;
 extern const char *repertoire_global;
 extern int max_locarchive_open_retry;
 extern bool no_archive;
@@ -122,19 +125,6 @@  extern const char *alias_file;
 #include <programs/xmalloc.h>
 
 
-/* Wrapper to switch LC_CTYPE back to the locale specified in the
-   environment for output.  */
-#define WITH_CUR_LOCALE(stmt)					\
-  do {								\
-      int saved_errno = errno;					\
-      const char *cur_locale_ = setlocale (LC_CTYPE, NULL);	\
-      setlocale (LC_CTYPE, "");					\
-      errno = saved_errno; 					\
-      stmt;							\
-      setlocale (LC_CTYPE, cur_locale_);			\
-  } while (0)
-
-
 /* Mark given locale as to be read.  */
 extern struct localedef_t *add_to_readlist (int locale, const char *name,
 					    const char *repertoire_name,
diff --git a/locale/programs/locarchive.c b/locale/programs/locarchive.c
index f67b7b8..633c59b 100644
--- a/locale/programs/locarchive.c
+++ b/locale/programs/locarchive.c
@@ -320,8 +320,8 @@  compare_from_file (struct locarhandle *ah, void *p1, uint32_t offset2,
 {
   void *p2 = xmalloc (size);
   if (pread (ah->fd, p2, size, offset2) != size)
-    WITH_CUR_LOCALE (error (4, errno,
-			    _("cannot read data from locale archive")));
+    record_error (4, errno,
+		  _("cannot read data from locale archive"));
 
   int res = memcmp (p1, p2, size);
   free (p2);
diff --git a/locale/programs/locfile.c b/locale/programs/locfile.c
index 0990ef1..b52efcf 100644
--- a/locale/programs/locfile.c
+++ b/locale/programs/locfile.c
@@ -796,9 +796,8 @@  write_locale_data (const char *output_path, int catidx, const char *category,
 
       if (fd == -1)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, save_err, _("\
-cannot open output file `%s' for category `%s'"), fname, category));
+	  record_error (0, save_err, _("\
+cannot open output file `%s' for category `%s'"), fname, category);
 	  free (fname);
 	  return;
 	}
@@ -820,9 +819,8 @@  cannot open output file `%s' for category `%s'"), fname, category));
 
       if (writev (fd, &vec[cnt], step) < 0)
 	{
-	  if (!be_quiet)
-	    WITH_CUR_LOCALE (error (0, errno, _("\
-failure while writing data for category `%s'"), category));
+	  record_error (0, errno, _("\
+failure while writing data for category `%s'"), category);
 	  break;
 	}
     }
@@ -916,9 +914,8 @@  failure while writing data for category `%s'"), category));
 			      unlink (fname);
 			      if (rename (tmp_fname, fname) < 0)
 				{
-				  if (!be_quiet)
-				    WITH_CUR_LOCALE (error (0, errno, _("\
-cannot create output file `%s' for category `%s'"), fname, category));
+				  record_error (0, errno, _("\
+cannot create output file `%s' for category `%s'"), fname, category);
 				}
 			      free (tmp_fname);
 			      free (other_fname);
diff --git a/locale/programs/record-status.h b/locale/programs/record-status.h
new file mode 100644
index 0000000..5edc20c
--- /dev/null
+++ b/locale/programs/record-status.h
@@ -0,0 +1,229 @@ 
+/* General definitions for recording error and warning status.
+   Copyright (C) 1998-2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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; version 2 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, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef _RECORD_STATUS_H
+#define _RECORD_STATUS_H 1
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <error.h>
+#include <locale.h>
+#include <string.h>
+
+/* We tentatively define all of the global data we use:
+   * recorded_warning_count: Number of warnings counted.
+   * recorded_error_count: Number of errors counted.
+   * be_quiet: Should all calls be silent?
+   * verbose: Should verbose messages be printed?  */
+int recorded_warning_count;
+int recorded_error_count;
+int be_quiet;
+int verbose;
+
+/* Saved locale state.  */
+struct locale_state
+{
+   /* The current in-use locale.  */
+   char *cur_locale;
+};
+
+/* Alter the current locale to match the locale configured by the
+   user, and return the previous saved state.  */
+static inline struct locale_state
+push_locale (void)
+{
+  int saved_errno;
+  const char *orig;
+  char *copy = NULL;
+
+  saved_errno = errno;
+
+  orig = setlocale (LC_CTYPE, NULL);
+  if (orig == NULL)
+    error (0, 0, "failed to read locale!");
+
+  if (setlocale (LC_CTYPE, "") == NULL)
+    error (0, 0, "failed to set locale!");
+
+  errno = saved_errno;
+
+  if (orig != NULL)
+    copy = strdup (orig);
+
+  /* We will return either a valid locale or NULL if we failed
+     to save the locale.  */
+  return (struct locale_state) { .cur_locale = copy };
+}
+
+/* Use the saved state to restore the locale.  */
+static inline void
+pop_locale (struct locale_state ls)
+{
+  const char *set = NULL;
+  /* We might have failed to save the locale, so only attempt to
+     restore a validly saved non-NULL locale.  */
+  if (ls.cur_locale != NULL)
+    {
+      set = setlocale (LC_CTYPE, ls.cur_locale);
+      if (set == NULL)
+	error (0, 0, "failed to restore %s locale!", ls.cur_locale);
+
+      free (ls.cur_locale);
+    }
+}
+
+/* Wrapper to print verbose informative messages.
+   Verbose messages are only printed if --verbose
+   is in effect and --quiet is not.  */
+static inline void
+__attribute__ ((__format__ (__printf__, 2, 3), nonnull (1, 2)))
+record_verbose (FILE *stream, const char *format, ...)
+{
+  char *str;
+  va_list arg;
+
+  if (!verbose)
+    return;
+
+  if (!be_quiet)
+    {
+      struct locale_state ls;
+      int ret;
+
+      va_start (arg, format);
+      ls = push_locale ();
+
+      ret = vasprintf (&str, format, arg);
+      if (ret == -1)
+	abort ();
+
+      pop_locale (ls);
+      va_end (arg);
+
+      fputs (str, stream);
+
+      free (str);
+    }
+}
+
+/* Wrapper to print warning messages.  We keep track of how
+   many were called because this effects our exit code.
+   Nothing is printed if --quiet is in effect, but warnings
+   are always counted.  */
+static inline void
+__attribute__ ((__format__ (__printf__, 1, 2), nonnull (1)))
+record_warning (const char *format, ...)
+{
+  char *str;
+  va_list arg;
+
+  recorded_warning_count++;
+
+  if (!be_quiet)
+    {
+      struct locale_state ls;
+      int ret;
+
+      va_start (arg, format);
+      ls = push_locale ();
+
+      ret = vasprintf (&str, format, arg);
+      if (ret == -1)
+	abort ();
+
+      pop_locale (ls);
+      va_end (arg);
+
+      fprintf (stderr, "%s", str);
+
+      free (str);
+    }
+}
+
+/* Wrapper to print error messages.  We keep track of how
+   many were called because this effects our exit code.
+   Nothing is printed if --quiet is in effect, but errors
+   are always counted, and fatal errors always exit the
+   program.  */
+static inline void
+__attribute__ ((__format__ (__printf__, 3, 4), nonnull (3)))
+record_error (int status, int errnum, const char *format, ...)
+{
+  char *str;
+  va_list arg;
+
+  recorded_error_count++;
+
+  if (!be_quiet)
+    {
+      struct locale_state ls;
+      int ret;
+
+      va_start (arg, format);
+      ls = push_locale ();
+
+      ret = vasprintf (&str, format, arg);
+      if (ret == -1)
+        abort ();
+
+      pop_locale (ls);
+      va_end (arg);
+
+      error (status, errnum, "%s", str);
+
+      free (str);
+    }
+
+  if (status != 0)
+    exit (status);
+}
+/* ... likewise for error_at_line.  */
+static inline void
+__attribute__ ((__format__ (__printf__, 5, 6), nonnull (3, 5)))
+record_error_at_line (int status, int errnum, const char *filename,
+		      unsigned int linenum, const char *format, ...)
+{
+  char *str;
+  va_list arg;
+
+  recorded_error_count++;
+
+  if (!be_quiet)
+    {
+      struct locale_state ls;
+      int ret;
+
+      va_start (arg, format);
+      ls = push_locale ();
+
+      ret = vasprintf (&str, format, arg);
+      if (ret == -1)
+        abort ();
+
+      pop_locale (ls);
+      va_end (arg);
+
+      error_at_line (status, errnum, filename, linenum, "%s", str);
+
+      free (str);
+    }
+
+  if (status != 0)
+    exit (status);
+}
+
+#endif
diff --git a/locale/programs/repertoire.c b/locale/programs/repertoire.c
index 61f2c05..68f0f42 100644
--- a/locale/programs/repertoire.c
+++ b/locale/programs/repertoire.c
@@ -20,7 +20,6 @@ 
 #endif
 
 #include <errno.h>
-#include <error.h>
 #include <limits.h>
 #include <obstack.h>
 #include <search.h>
@@ -321,14 +320,14 @@  argument to <%s> must be a single character"),
     }
 
   if (state != 2 && state != 90 && !be_quiet)
-    WITH_CUR_LOCALE (error (0, 0, _("%s: premature end of file"),
-			    repfile->fname));
+    record_error (0, 0, _("%s: premature end of file"),
+		  repfile->fname);
 
   lr_close (repfile);
 
   if (tsearch (result, &known, &repertoire_compare) == NULL)
     /* Something went wrong.  */
-    WITH_CUR_LOCALE (error (0, errno, _("cannot save new repertoire map")));
+    record_error (0, errno, _("cannot save new repertoire map"));
 
   return result;
 }
@@ -339,8 +338,8 @@  repertoire_complain (const char *name)
 {
   if (tfind (name, &unavailable, (__compar_fn_t) strcmp) == NULL)
     {
-      WITH_CUR_LOCALE (error (0, errno, _("\
-repertoire map file `%s' not found"), name));
+      record_error (0, errno, _("\
+repertoire map file `%s' not found"), name);
 
       /* Remember that we reported this map.  */
       tsearch (name, &unavailable, (__compar_fn_t) strcmp);
-- 
2.9.5