Use C99-compliant scanf under _GNU_SOURCE.

Message ID 20180210181736.11570-1-zackw@panix.com
State Committed
Headers

Commit Message

Zack Weinberg Feb. 10, 2018, 6:17 p.m. UTC
  The only difference between noncompliant and C99-compliant scanf is
that the former accepts the archaic GNU extension '%as' (also %aS and
%a[...]) meaning to allocate space for the input string with malloc.
This extension conflicts with C99's use of %a as a format _type_
meaning to read a floating-point number; POSIX.1-2001 standardized
equivalent functionality using the modifier letter 'm' instead (%ms,
%mS, %m[...]).

The extension was already disabled in most conformance modes:
specifically, any mode that doesn't involve _GNU_SOURCE and _does_
involve either strict conformance to C99 or loose conformance to both
C99 and POSIX.1-2001 would get the C99-compliant scanf.  With
compilers new enough to use -std=gnu11 instead of -std=gnu89, or
equivalent, that includes the default mode.  Moreover, GCC isn't aware
of the subtleties of the above paragraph when issuing -Wformat
warnings; if you give -std=gnu99 or above you get dinged for using %a
with the archaic-GNU meaning.

This change is fiddlier than just changing a couple of #if
expressions, for several reasons.  The most obvious of these is that
we need to be able to compile the noncompliant scanf itself.  This is
the same problem we had when we removed 'gets' from _GNU_SOURCE and
it's dealt with the same way: there's a new __GLIBC_USE symbol,
DEPRECATED_SCANF, which defaults to off under the appropriate
conditions for external code, but can be overridden by individual
files within stdio.

We also run into problems with PLT bypass, because libc_hidden_proto
uses __REDIRECT and so does the logic in stdio.h for choosing which
implementation of scanf to use; __REDIRECT isn't transitive, so a
couple of files that use sscanf internally have to bridge the gap with
macros.  I'm open to better ideas if anyone has one (N.B. I would
accept "internal code shouldn't call *scanf ever" as a better idea,
but I'm not gonna make that decision unilaterally).  Fortunately,
elf/check-localplt will catch any new internal uses of *scanf that
aren't shimmed.

Finally, there are several tests in stdio-common that use the
extension.  bug21 is a regression test for a crash, and still
exercises the relevant code if changed to use %ms instead of %as.
scanf14 through scanf17 are more complicated since they are actually
testing the subtleties of the extension - under what circumstances is
'a' treated as a modifier letter, etc.  My approach here is to
duplicate scanf14.c and scanf16.c; the originals change to use %ms
instead, the copies select precisely the right conformance mode to get
%as with the old GNU meaning, plus everything else they need (it's not
as simple as saying -std=gnu89, unfortunately).  scanf15 and scanf17
become simpler because they no longer need to avoid _GNU_SOURCE, and
all of them no longer need diagnostic overrides.  Yay!

I kinda want to relax the condition for C99-compliant scanf a bit
further, from

+#if defined __USE_ISOC99 && (defined __USE_XOPEN2K || defined __STRICT_ANSI__)

to

+#if defined __USE_ISOC99 || defined __USE_XOPEN2K

The major problem with that is, there would then be no way to get the
noncompliant *v*scanf without overriding __GLIBC_USE_DEPRECATED_SCANF,
because vscanf/vfscanf/vsscanf themselves are only available under
__USE_ISOC99.  Not a problem we have to solve today, though.

--
	* include/features.h (__GLIBC_USE_DEPRECATED_SCANF): New macro.
	Don't consider __USE_GNU when deciding whether to use deprecated scanf.
        * include/stdio.h: Provide libc_hidden_proto for __isoc99_sscanf,
	not sscanf.
	* libio/stdio.h, libio/bits/stdio-ldbl.h: Use
	__GLIBC_USE(DEPRECATED_SCANF) to decide whether or not to redirect
	scanf, fscanf, sscanf, vscanf, vfscanf, and vsscanf to their
	__isoc99_ variants.

	* libio/iovsscanf.c, libio/vscanf.c, stdio-common/fscanf.c
	* stdio-common/scanf.c, stdio-common/vfscanf.c:
	Override __GLIBC_USE_DEPRECATED_SCANF to 1.
	* stdio-common/sscanf.c: Likewise.  Remove ldbl_hidden_def for
	__sscanf.
	* stdio-common/isoc99_sscanf.c: Add libc_hidden_def for __isoc99_sscanf.

	* misc/mntent_r.c, time/tzset.c: Redirect sscanf to __isoc99_scanf
	with a macro.

	* stdio-common/bug21.c, stdio-common/scanf14.c:
	Use %ms instead of %as, %mS instead of %aS, %m[] instead of %a[];
	remove DIAG_IGNORE_NEEDS_COMMENT for -Wformat.
	* stdio-common/scanf16.c: Likewise.  Add __attribute__((format(scanf)))
	to xscanf, xfscanf, xsscanf.

	* stdio-common/scanf14a.c: New copy of scanf14.c which still uses
	%as, %aS, %a[].  Use conformance mode -std=gnu89,
	_POSIX_C_SOURCE=199506L, _XOPEN_SOURCE=1, _XOPEN_SOURCE_EXTENDED=1,
	which will use the nonconformant scanf implementation.
	Remove DIAG_IGNORE_NEEDS_COMMENT for -Wformat.
	* stdio-common/scanf16a.c: New copy of scanf16.c which still uses
	%as, %aS, %a[].  Use conformance mode -std=gnu89,
	_POSIX_C_SOURCE=199506L, _XOPEN_SOURCE=1, _XOPEN_SOURCE_EXTENDED=1,
	_ISOC99_SOURCE=1, which will use the nonconformant scanf
	implementation.  Add __attribute__((format(scanf))) to xscanf,
	xfscanf, xsscanf.

	* stdio-common/scanf15.c, stdio-common/scanf17.c: No need to
	override feature selection macros or provide definitions of u_char etc.
	* stdio-common/Makefile (tests): Add scanf14a and scanf16a.
	(CFLAGS-scanf15.c, CFLAGS-scanf17.c): Remove.
	(CFLAGS-scanf14a.c, CFLAGS-scanf16a.c): New.
---
 NEWS                         |  15 ++++
 include/features.h           |  15 ++++
 include/stdio.h              |   2 +-
 libio/bits/stdio-ldbl.h      |   7 +-
 libio/iovsscanf.c            |   5 ++
 libio/stdio.h                |  26 +++----
 libio/vscanf.c               |   5 ++
 misc/mntent_r.c              |   4 ++
 stdio-common/Makefile        |  13 ++--
 stdio-common/bug21.c         |  11 +--
 stdio-common/fscanf.c        |   5 ++
 stdio-common/isoc99_sscanf.c |   1 +
 stdio-common/scanf.c         |   5 ++
 stdio-common/scanf14.c       |  31 ++------
 stdio-common/scanf14a.c      | 133 ++++++++++++++++++++++++++++++++++
 stdio-common/scanf15.c       |  10 ---
 stdio-common/scanf16.c       |  16 ++---
 stdio-common/scanf16a.c      | 165 +++++++++++++++++++++++++++++++++++++++++++
 stdio-common/scanf17.c       |  10 ---
 stdio-common/sscanf.c        |   6 +-
 stdio-common/vfscanf.c       |   5 ++
 time/tzset.c                 |   4 ++
 22 files changed, 400 insertions(+), 94 deletions(-)
 create mode 100644 stdio-common/scanf14a.c
 create mode 100644 stdio-common/scanf16a.c
  

Comments

Rical Jasan Feb. 12, 2018, 12:53 p.m. UTC | #1
On 02/10/2018 10:17 AM, Zack Weinberg wrote:
> The only difference between noncompliant and C99-compliant scanf is
> that the former accepts the archaic GNU extension '%as' (also %aS and
> %a[...]) meaning to allocate space for the input string with malloc.
> This extension conflicts with C99's use of %a as a format _type_
> meaning to read a floating-point number; POSIX.1-2001 standardized

POSIX.1-2008, from what I could tell (Issue 7 of IEEE 1003.1, if I'm
saying that right).  This happens a few times throughout.

> equivalent functionality using the modifier letter 'm' instead (%ms,
> %mS, %m[...]).
...
> diff --git a/NEWS b/NEWS
> index 60dd2f778d9..45465b785e3 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -28,6 +28,21 @@ Deprecated and removed features, and other changes affecting compatibility:
>     investigate using (f)getc_unlocked and (f)putc_unlocked, and, if
>     necessary, flockfile and funlockfile.
>  
> + * The scanf formats '%as', '%aS', and '%a[...]', meaning to read a string
> +   and allocate space for it using malloc, are no longer accepted in code
> +   compiled with _GNU_SOURCE.  They were already not accepted in code
> +   compiled in the default mode with modern compilers (e.g. GCC new enough
> +   to default to -std=gnu11, without _GNU_SOURCE).
> +
> +   Using 'a' as a modifier letter for 's'-type scanf formats is an archaic
> +   GNU extension that conflicts with C99's use of '%a' for floating-point
> +   numbers.  Equivalent functionality was standardized in POSIX.1-2001 using
> +   the letter 'm' instead; programs using '%as', '%aS', and '%a[...]' should
> +   change to '%ms', '%mS', and '%m[...]' respectively.  GCC's -Wformat
> +   warnings can detect most uses of the extension, as long as all functions
> +   that call vscanf, vfscanf, or vsscanf are annotated with
> +   __attribute__((format(scanf, ...))).
> +
>   * The macros 'major', 'minor', and 'makedev' are now only available from
>     the header <sys/sysmacros.h>; not from <sys/types.h> or various other
>     headers that happen to include <sys/types.h>.  These macros are rarely
> diff --git a/include/features.h b/include/features.h
> index 137a90b4055..e6b19c5789c 100644
> --- a/include/features.h
> +++ b/include/features.h
> @@ -140,6 +140,7 @@
>  #undef	__USE_FORTIFY_LEVEL
>  #undef	__KERNEL_STRICT_NAMES
>  #undef	__GLIBC_USE_DEPRECATED_GETS
> +#undef  __GLIBC_USE_DEPRECATED_SCANF

Should be a tab.

>  
>  /* Suppress kernel-name space pollution unless user expressedly asks
>     for it.  */
> @@ -401,6 +402,20 @@
>  # define __GLIBC_USE_DEPRECATED_GETS 1
>  #endif
>  
> +/* GNU formerly extended the 'scanf' functions with modified format
> +   specifiers %as, %aS, and %a[...] that allocate a buffer for the
> +   input using malloc.  This extension conflicts with ISO C99, which
> +   defines %a as a standalone format specifier that reads a
> +   floating-point number; moreover, POSIX.1-2001 provides the same
> +   functionality using the modifier letter 'm' instead (%ms, %mS,
> +   %m[...]).  We now follow C99 and POSIX unless an old, non-strict
> +   conformance level is specifically selected.  */
> +#if defined __USE_ISOC99 && (defined __USE_XOPEN2K || defined __STRICT_ANSI__)
> +# define __GLIBC_USE_DEPRECATED_SCANF 0
> +#else
> +# define __GLIBC_USE_DEPRECATED_SCANF 1
> +#endif
> +
>  /* Get definitions of __STDC_* predefined macros, if the compiler has
>     not preincluded this header automatically.  */
>  #include <stdc-predef.h>
> diff --git a/include/stdio.h b/include/stdio.h
> index 94bc2fdc7ef..d0e9343b2a9 100644
> --- a/include/stdio.h
> +++ b/include/stdio.h
> @@ -67,6 +67,7 @@ extern int __isoc99_vscanf (const char *__restrict __format,
>  extern int __isoc99_vsscanf (const char *__restrict __s,
>  			     const char *__restrict __format,
>  			     __gnuc_va_list __arg) __THROW;
> +libc_hidden_proto (__isoc99_sscanf)
>  libc_hidden_proto (__isoc99_vsscanf)
>  libc_hidden_proto (__isoc99_vfscanf)
>  
> @@ -154,7 +155,6 @@ libc_hidden_proto (__dprintf)
>  libc_hidden_proto (fprintf)
>  libc_hidden_proto (vfprintf)
>  libc_hidden_proto (sprintf)
> -libc_hidden_proto (sscanf)
>  libc_hidden_proto (fwrite)
>  libc_hidden_proto (perror)
>  libc_hidden_proto (remove)
> diff --git a/libio/bits/stdio-ldbl.h b/libio/bits/stdio-ldbl.h
> index 99d9bcc2334..ab55cbe7fb2 100644
> --- a/libio/bits/stdio-ldbl.h
> +++ b/libio/bits/stdio-ldbl.h
> @@ -26,9 +26,7 @@ __LDBL_REDIR_DECL (sprintf)
>  __LDBL_REDIR_DECL (vfprintf)
>  __LDBL_REDIR_DECL (vprintf)
>  __LDBL_REDIR_DECL (vsprintf)
> -#if defined __USE_ISOC99 && !defined __USE_GNU \
> -    && !defined __REDIRECT \
> -    && (defined __STRICT_ANSI__ || defined __USE_XOPEN2K)
> +#if !__GLIBC_USE (DEPRECATED_SCANF) && defined __REDIRECT
>  __LDBL_REDIR1_DECL (fscanf, __nldbl___isoc99_fscanf)
>  __LDBL_REDIR1_DECL (scanf, __nldbl___isoc99_scanf)
>  __LDBL_REDIR1_DECL (sscanf, __nldbl___isoc99_sscanf)
> @@ -44,8 +42,7 @@ __LDBL_REDIR_DECL (vsnprintf)
>  #endif
>  
>  #ifdef	__USE_ISOC99
> -# if !defined __USE_GNU && !defined __REDIRECT \
> -     && (defined __STRICT_ANSI__ || defined __USE_XOPEN2K)
> +#if !__GLIBC_USE (DEPRECATED_SCANF) && defined __REDIRECT

This #if should still be indented one space, I believe.  You might be
able to get rid of the outer "#ifdef __USE_ISOC99" though, since it only
wraps this this one block.

>  __LDBL_REDIR1_DECL (vfscanf, __nldbl___isoc99_vfscanf)
>  __LDBL_REDIR1_DECL (vscanf, __nldbl___isoc99_vscanf)
>  __LDBL_REDIR1_DECL (vsscanf, __nldbl___isoc99_vsscanf)
> diff --git a/libio/iovsscanf.c b/libio/iovsscanf.c
> index b5514fc74ea..e072258ba30 100644
> --- a/libio/iovsscanf.c
> +++ b/libio/iovsscanf.c
> @@ -24,6 +24,11 @@
>     This exception applies to code released by its copyright holders
>     in files containing the exception.  */
>  
> +/* This file defines one of the deprecated scanf variants.  */
> +#include <features.h>
> +#undef __GLIBC_USE_DEPRECATED_SCANF
> +#define __GLIBC_USE_DEPRECATED_SCANF 1
> +
>  #include "libioP.h"
>  #include "strfile.h"
>  
> diff --git a/libio/stdio.h b/libio/stdio.h
> index 731f8e56f4c..a04539639db 100644
> --- a/libio/stdio.h
> +++ b/libio/stdio.h
> @@ -387,13 +387,11 @@ extern int scanf (const char *__restrict __format, ...) __wur;
>  extern int sscanf (const char *__restrict __s,
>  		   const char *__restrict __format, ...) __THROW;
>  
> -#if defined __USE_ISOC99 && !defined __USE_GNU \
> -    && (!defined __LDBL_COMPAT || !defined __REDIRECT) \
> -    && (defined __STRICT_ANSI__ || defined __USE_XOPEN2K)
> -# ifdef __REDIRECT
> -/* For strict ISO C99 or POSIX compliance disallow %as, %aS and %a[
> -   GNU extension which conflicts with valid %a followed by letter
> -   s, S or [.  */
> +/* For historical reasons, the C99-compliant versions of the scanf
> +   functions are at alternative names.  When __LDBL_COMPAT is in
> +   effect, this is handled in bits/stdio-ldbl.h.  */
> +#if !__GLIBC_USE (DEPRECATED_SCANF)
> +# if defined __REDIRECT && !defined __LDBL_COMPAT
>  extern int __REDIRECT (fscanf, (FILE *__restrict __stream,
>  				const char *__restrict __format, ...),
>  		       __isoc99_fscanf) __wur;
> @@ -402,7 +400,7 @@ extern int __REDIRECT (scanf, (const char *__restrict __format, ...),
>  extern int __REDIRECT_NTH (sscanf, (const char *__restrict __s,
>  				    const char *__restrict __format, ...),
>  			   __isoc99_sscanf);
> -# else
> +# elif !defined __REDIRECT

Should this still include the "!defined __LDBL_COMPAT"?  Or maybe put
that on the line with DEPRECATED_SCANF, so it also wraps the __REDIRECT
if-else?

>  extern int __isoc99_fscanf (FILE *__restrict __stream,
>  			    const char *__restrict __format, ...) __wur;
>  extern int __isoc99_scanf (const char *__restrict __format, ...) __wur;
> @@ -435,13 +433,9 @@ extern int vsscanf (const char *__restrict __s,
>  		    const char *__restrict __format, __gnuc_va_list __arg)
>       __THROW __attribute__ ((__format__ (__scanf__, 2, 0)));
>  
> -# if !defined __USE_GNU \
> -     && (!defined __LDBL_COMPAT || !defined __REDIRECT) \
> -     && (defined __STRICT_ANSI__ || defined __USE_XOPEN2K)
> -#  ifdef __REDIRECT
> -/* For strict ISO C99 or POSIX compliance disallow %as, %aS and %a[
> -   GNU extension which conflicts with valid %a followed by letter
> -   s, S or [.  */
> +/* Same redirection as above for the v*scanf family.  */
> +# if !__GLIBC_USE (DEPRECATED_SCANF)
> +#  if defined __REDIRECT && !defined __LDBL_COMPAT
>  extern int __REDIRECT (vfscanf,
>  		       (FILE *__restrict __s,
>  			const char *__restrict __format, __gnuc_va_list __arg),
> @@ -455,7 +449,7 @@ extern int __REDIRECT_NTH (vsscanf,
>  			    const char *__restrict __format,
>  			    __gnuc_va_list __arg), __isoc99_vsscanf)
>       __attribute__ ((__format__ (__scanf__, 2, 0)));
> -#  else
> +#  elif !defined __REDIRECT
>  extern int __isoc99_vfscanf (FILE *__restrict __s,
>  			     const char *__restrict __format,
>  			     __gnuc_va_list __arg) __wur;

Rical
  
Joseph Myers Feb. 12, 2018, 4:25 p.m. UTC | #2
On Sat, 10 Feb 2018, Zack Weinberg wrote:

> +#if defined __USE_ISOC99 || defined __USE_XOPEN2K

That's equivalent to just #ifdef __USE_ISOC99 (since __USE_XOPEN2K implies 
__USE_ISOC99; POSIX.1-2001 is based on C99).
  
Zack Weinberg Feb. 12, 2018, 4:33 p.m. UTC | #3
On Mon, Feb 12, 2018 at 11:25 AM, Joseph Myers <joseph@codesourcery.com> wrote:
> On Sat, 10 Feb 2018, Zack Weinberg wrote:
>
>> +#if defined __USE_ISOC99 || defined __USE_XOPEN2K
>
> That's equivalent to just #ifdef __USE_ISOC99 (since __USE_XOPEN2K implies
> __USE_ISOC99; POSIX.1-2001 is based on C99).

Well, that certainly throws the question of how anyone could possibly
use the noncompliant vscanf into stark relief.

zw
  
Zack Weinberg Feb. 12, 2018, 4:38 p.m. UTC | #4
On Mon, Feb 12, 2018 at 11:33 AM, Zack Weinberg <zackw@panix.com> wrote:
> On Mon, Feb 12, 2018 at 11:25 AM, Joseph Myers <joseph@codesourcery.com> wrote:
>> On Sat, 10 Feb 2018, Zack Weinberg wrote:
>>
>>> +#if defined __USE_ISOC99 || defined __USE_XOPEN2K
>>
>> That's equivalent to just #ifdef __USE_ISOC99 (since __USE_XOPEN2K implies
>> __USE_ISOC99; POSIX.1-2001 is based on C99).
>
> Well, that certainly throws the question of how anyone could possibly
> use the noncompliant vscanf into stark relief.

Presumably the history here is that we had v{,f,s}scanf before C99 did
(I haven't checked).  What if we use C99-complaint everything for any
mode involving __ISOC99_SOURCE, but we also relax the condition for
declaring v{,f,s}scanf in the first place, from __ISOC99_SOURCE to
(__ISOC99_SOURCE || !__STRICT_ANSI__)?  In other words, provide
v{,f,s}scanf under -std=gnu89 as well as under -std={c,gnu}99 and up.

(I care about this because, if you can't get at the noncompliant
vscanf without overriding __GLIBC_USE_DEPRECATED_SCANF, then why do we
have the noncompliant vscanf at all?)

zw
  
Joseph Myers Feb. 12, 2018, 5:51 p.m. UTC | #5
On Mon, 12 Feb 2018, Zack Weinberg wrote:

> Presumably the history here is that we had v{,f,s}scanf before C99 did
> (I haven't checked).  What if we use C99-complaint everything for any
> mode involving __ISOC99_SOURCE, but we also relax the condition for
> declaring v{,f,s}scanf in the first place, from __ISOC99_SOURCE to
> (__ISOC99_SOURCE || !__STRICT_ANSI__)?  In other words, provide
> v{,f,s}scanf under -std=gnu89 as well as under -std={c,gnu}99 and up.

It's odd to use __STRICT_ANSI__ as a condition for whether to declare 
something in a header.  We sometimes use __USE_MISC, but that's only 
defined for _DEFAULT_SOURCE which also implies __USE_ISOC99.

> (I care about this because, if you can't get at the noncompliant
> vscanf without overriding __GLIBC_USE_DEPRECATED_SCANF, then why do we
> have the noncompliant vscanf at all?)

Indeed, it could become a compat symbol and not defined for new ports / 
static linking if there's no longer any way of accessing it.
  
Zack Weinberg Feb. 21, 2018, 6:01 p.m. UTC | #6
On Sat, Feb 10, 2018 at 1:17 PM, Zack Weinberg <zackw@panix.com> wrote:
> The only difference between noncompliant and C99-compliant scanf is
> that the former accepts the archaic GNU extension '%as' (also %aS and
> %a[...]) meaning to allocate space for the input string with malloc.
> This extension conflicts with C99's use of %a as a format _type_
> meaning to read a floating-point number; POSIX.1-2001 standardized
> equivalent functionality using the modifier letter 'm' instead (%ms,
> %mS, %m[...]).

Ping.  The details of this patch have been reviewed (thanks, Rical)
but I would like to hear opinions from more people than just me and
Joseph on which feature-test macros should expose the old scanf
family.  We want this to match GCC's diagnostic behavior as closely as
possible, and we also want the default mode to be C99-compliant.  The
tricky cases are e.g. -std=c89 -D_GNU_SOURCE should probably get old
scanf, and should it also get old vscanf?  vscanf is currently treated
as new in C99, but was a GNU extension before then, and programs that
are using it expecting GNU %as won't be caught by the compiler.

There also seems to be some confusion about which revision of POSIX
added %ms: comparing
http://pubs.opengroup.org/onlinepubs/9699919799/functions/fscanf.html
to http://pubs.opengroup.org/onlinepubs/009695399/functions/fscanf.html
leads me to believe that it was added in POSIX.1-2008, but it's (sort
of) gated on __USE_XOPEN2K right now, which corresponds to
POSIX.1-2001, not -2008.  "SD5-XSH-ERN-132 is applied" in the change
history on the Issue 7 version of the page makes me think it might
have been added in a minor update to -2001, but then why doesn't it
show up on the online version of -2001?

zw
  
Joseph Myers Feb. 21, 2018, 6:08 p.m. UTC | #7
On Wed, 21 Feb 2018, Zack Weinberg wrote:

> leads me to believe that it was added in POSIX.1-2008, but it's (sort
> of) gated on __USE_XOPEN2K right now, which corresponds to

That's probably on the basis of POSIX.1-2001 implying C99 (so implying C99 
%a semantics).

> POSIX.1-2001, not -2008.  "SD5-XSH-ERN-132 is applied" in the change
> history on the Issue 7 version of the page makes me think it might
> have been added in a minor update to -2001, but then why doesn't it
> show up on the online version of -2001?

Because that was a non-bug-fix enhancement request, so a new feature only 
for the 2008 edition, not the 2004 bug-fix edition (this predates the 
present issue tracking system, wherein you have explicit "issue8" tags to 
indicate more explicitly feature changes that are only intended for the 
next major revision of POSIX).
  
Zack Weinberg Feb. 22, 2018, 1:04 a.m. UTC | #8
On Mon, Feb 12, 2018 at 7:53 AM, Rical Jasan <ricaljasan@pacific.net> wrote:
> On 02/10/2018 10:17 AM, Zack Weinberg wrote:
>> The only difference between noncompliant and C99-compliant scanf is
>> that the former accepts the archaic GNU extension '%as' (also %aS and
>> %a[...]) meaning to allocate space for the input string with malloc.
>> This extension conflicts with C99's use of %a as a format _type_
>> meaning to read a floating-point number; POSIX.1-2001 standardized
>
> POSIX.1-2008, from what I could tell (Issue 7 of IEEE 1003.1, if I'm
> saying that right).  This happens a few times throughout.

Fixed, thanks.

>> +#undef  __GLIBC_USE_DEPRECATED_SCANF
>
> Should be a tab.

Fixed.

>>  #ifdef       __USE_ISOC99
>> -# if !defined __USE_GNU && !defined __REDIRECT \
>> -     && (defined __STRICT_ANSI__ || defined __USE_XOPEN2K)
>> +#if !__GLIBC_USE (DEPRECATED_SCANF) && defined __REDIRECT
>
> This #if should still be indented one space, I believe.

Yup, fixed.

> You might be
> able to get rid of the outer "#ifdef __USE_ISOC99" though, since it only
> wraps this this one block.

I don't think I can.  Neither the inner if block nor the inner else
block should be exposed when __USE_ISOC99 is false.

>> -# else
>> +# elif !defined __REDIRECT
>
> Should this still include the "!defined __LDBL_COMPAT"?  Or maybe put
> that on the line with DEPRECATED_SCANF, so it also wraps the __REDIRECT
> if-else?

I can simplify it a little, but ... this gets messy, because
ldbl-compat.h isn't participating in the fiction that our headers work
with a compiler that doesn't provide __asm__() redirections. I am
tempted to put an #error in sys/cdefs.h and see if anything comes out
of the woodwork (tinycc, maybe?)

zw
  

Patch

diff --git a/NEWS b/NEWS
index 60dd2f778d9..45465b785e3 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,21 @@  Deprecated and removed features, and other changes affecting compatibility:
    investigate using (f)getc_unlocked and (f)putc_unlocked, and, if
    necessary, flockfile and funlockfile.
 
+ * The scanf formats '%as', '%aS', and '%a[...]', meaning to read a string
+   and allocate space for it using malloc, are no longer accepted in code
+   compiled with _GNU_SOURCE.  They were already not accepted in code
+   compiled in the default mode with modern compilers (e.g. GCC new enough
+   to default to -std=gnu11, without _GNU_SOURCE).
+
+   Using 'a' as a modifier letter for 's'-type scanf formats is an archaic
+   GNU extension that conflicts with C99's use of '%a' for floating-point
+   numbers.  Equivalent functionality was standardized in POSIX.1-2001 using
+   the letter 'm' instead; programs using '%as', '%aS', and '%a[...]' should
+   change to '%ms', '%mS', and '%m[...]' respectively.  GCC's -Wformat
+   warnings can detect most uses of the extension, as long as all functions
+   that call vscanf, vfscanf, or vsscanf are annotated with
+   __attribute__((format(scanf, ...))).
+
  * The macros 'major', 'minor', and 'makedev' are now only available from
    the header <sys/sysmacros.h>; not from <sys/types.h> or various other
    headers that happen to include <sys/types.h>.  These macros are rarely
diff --git a/include/features.h b/include/features.h
index 137a90b4055..e6b19c5789c 100644
--- a/include/features.h
+++ b/include/features.h
@@ -140,6 +140,7 @@ 
 #undef	__USE_FORTIFY_LEVEL
 #undef	__KERNEL_STRICT_NAMES
 #undef	__GLIBC_USE_DEPRECATED_GETS
+#undef  __GLIBC_USE_DEPRECATED_SCANF
 
 /* Suppress kernel-name space pollution unless user expressedly asks
    for it.  */
@@ -401,6 +402,20 @@ 
 # define __GLIBC_USE_DEPRECATED_GETS 1
 #endif
 
+/* GNU formerly extended the 'scanf' functions with modified format
+   specifiers %as, %aS, and %a[...] that allocate a buffer for the
+   input using malloc.  This extension conflicts with ISO C99, which
+   defines %a as a standalone format specifier that reads a
+   floating-point number; moreover, POSIX.1-2001 provides the same
+   functionality using the modifier letter 'm' instead (%ms, %mS,
+   %m[...]).  We now follow C99 and POSIX unless an old, non-strict
+   conformance level is specifically selected.  */
+#if defined __USE_ISOC99 && (defined __USE_XOPEN2K || defined __STRICT_ANSI__)
+# define __GLIBC_USE_DEPRECATED_SCANF 0
+#else
+# define __GLIBC_USE_DEPRECATED_SCANF 1
+#endif
+
 /* Get definitions of __STDC_* predefined macros, if the compiler has
    not preincluded this header automatically.  */
 #include <stdc-predef.h>
diff --git a/include/stdio.h b/include/stdio.h
index 94bc2fdc7ef..d0e9343b2a9 100644
--- a/include/stdio.h
+++ b/include/stdio.h
@@ -67,6 +67,7 @@  extern int __isoc99_vscanf (const char *__restrict __format,
 extern int __isoc99_vsscanf (const char *__restrict __s,
 			     const char *__restrict __format,
 			     __gnuc_va_list __arg) __THROW;
+libc_hidden_proto (__isoc99_sscanf)
 libc_hidden_proto (__isoc99_vsscanf)
 libc_hidden_proto (__isoc99_vfscanf)
 
@@ -154,7 +155,6 @@  libc_hidden_proto (__dprintf)
 libc_hidden_proto (fprintf)
 libc_hidden_proto (vfprintf)
 libc_hidden_proto (sprintf)
-libc_hidden_proto (sscanf)
 libc_hidden_proto (fwrite)
 libc_hidden_proto (perror)
 libc_hidden_proto (remove)
diff --git a/libio/bits/stdio-ldbl.h b/libio/bits/stdio-ldbl.h
index 99d9bcc2334..ab55cbe7fb2 100644
--- a/libio/bits/stdio-ldbl.h
+++ b/libio/bits/stdio-ldbl.h
@@ -26,9 +26,7 @@  __LDBL_REDIR_DECL (sprintf)
 __LDBL_REDIR_DECL (vfprintf)
 __LDBL_REDIR_DECL (vprintf)
 __LDBL_REDIR_DECL (vsprintf)
-#if defined __USE_ISOC99 && !defined __USE_GNU \
-    && !defined __REDIRECT \
-    && (defined __STRICT_ANSI__ || defined __USE_XOPEN2K)
+#if !__GLIBC_USE (DEPRECATED_SCANF) && defined __REDIRECT
 __LDBL_REDIR1_DECL (fscanf, __nldbl___isoc99_fscanf)
 __LDBL_REDIR1_DECL (scanf, __nldbl___isoc99_scanf)
 __LDBL_REDIR1_DECL (sscanf, __nldbl___isoc99_sscanf)
@@ -44,8 +42,7 @@  __LDBL_REDIR_DECL (vsnprintf)
 #endif
 
 #ifdef	__USE_ISOC99
-# if !defined __USE_GNU && !defined __REDIRECT \
-     && (defined __STRICT_ANSI__ || defined __USE_XOPEN2K)
+#if !__GLIBC_USE (DEPRECATED_SCANF) && defined __REDIRECT
 __LDBL_REDIR1_DECL (vfscanf, __nldbl___isoc99_vfscanf)
 __LDBL_REDIR1_DECL (vscanf, __nldbl___isoc99_vscanf)
 __LDBL_REDIR1_DECL (vsscanf, __nldbl___isoc99_vsscanf)
diff --git a/libio/iovsscanf.c b/libio/iovsscanf.c
index b5514fc74ea..e072258ba30 100644
--- a/libio/iovsscanf.c
+++ b/libio/iovsscanf.c
@@ -24,6 +24,11 @@ 
    This exception applies to code released by its copyright holders
    in files containing the exception.  */
 
+/* This file defines one of the deprecated scanf variants.  */
+#include <features.h>
+#undef __GLIBC_USE_DEPRECATED_SCANF
+#define __GLIBC_USE_DEPRECATED_SCANF 1
+
 #include "libioP.h"
 #include "strfile.h"
 
diff --git a/libio/stdio.h b/libio/stdio.h
index 731f8e56f4c..a04539639db 100644
--- a/libio/stdio.h
+++ b/libio/stdio.h
@@ -387,13 +387,11 @@  extern int scanf (const char *__restrict __format, ...) __wur;
 extern int sscanf (const char *__restrict __s,
 		   const char *__restrict __format, ...) __THROW;
 
-#if defined __USE_ISOC99 && !defined __USE_GNU \
-    && (!defined __LDBL_COMPAT || !defined __REDIRECT) \
-    && (defined __STRICT_ANSI__ || defined __USE_XOPEN2K)
-# ifdef __REDIRECT
-/* For strict ISO C99 or POSIX compliance disallow %as, %aS and %a[
-   GNU extension which conflicts with valid %a followed by letter
-   s, S or [.  */
+/* For historical reasons, the C99-compliant versions of the scanf
+   functions are at alternative names.  When __LDBL_COMPAT is in
+   effect, this is handled in bits/stdio-ldbl.h.  */
+#if !__GLIBC_USE (DEPRECATED_SCANF)
+# if defined __REDIRECT && !defined __LDBL_COMPAT
 extern int __REDIRECT (fscanf, (FILE *__restrict __stream,
 				const char *__restrict __format, ...),
 		       __isoc99_fscanf) __wur;
@@ -402,7 +400,7 @@  extern int __REDIRECT (scanf, (const char *__restrict __format, ...),
 extern int __REDIRECT_NTH (sscanf, (const char *__restrict __s,
 				    const char *__restrict __format, ...),
 			   __isoc99_sscanf);
-# else
+# elif !defined __REDIRECT
 extern int __isoc99_fscanf (FILE *__restrict __stream,
 			    const char *__restrict __format, ...) __wur;
 extern int __isoc99_scanf (const char *__restrict __format, ...) __wur;
@@ -435,13 +433,9 @@  extern int vsscanf (const char *__restrict __s,
 		    const char *__restrict __format, __gnuc_va_list __arg)
      __THROW __attribute__ ((__format__ (__scanf__, 2, 0)));
 
-# if !defined __USE_GNU \
-     && (!defined __LDBL_COMPAT || !defined __REDIRECT) \
-     && (defined __STRICT_ANSI__ || defined __USE_XOPEN2K)
-#  ifdef __REDIRECT
-/* For strict ISO C99 or POSIX compliance disallow %as, %aS and %a[
-   GNU extension which conflicts with valid %a followed by letter
-   s, S or [.  */
+/* Same redirection as above for the v*scanf family.  */
+# if !__GLIBC_USE (DEPRECATED_SCANF)
+#  if defined __REDIRECT && !defined __LDBL_COMPAT
 extern int __REDIRECT (vfscanf,
 		       (FILE *__restrict __s,
 			const char *__restrict __format, __gnuc_va_list __arg),
@@ -455,7 +449,7 @@  extern int __REDIRECT_NTH (vsscanf,
 			    const char *__restrict __format,
 			    __gnuc_va_list __arg), __isoc99_vsscanf)
      __attribute__ ((__format__ (__scanf__, 2, 0)));
-#  else
+#  elif !defined __REDIRECT
 extern int __isoc99_vfscanf (FILE *__restrict __s,
 			     const char *__restrict __format,
 			     __gnuc_va_list __arg) __wur;
diff --git a/libio/vscanf.c b/libio/vscanf.c
index a74e5418261..4c4eca91cf5 100644
--- a/libio/vscanf.c
+++ b/libio/vscanf.c
@@ -24,6 +24,11 @@ 
    This exception applies to code released by its copyright holders
    in files containing the exception.  */
 
+/* This file defines one of the deprecated scanf variants.  */
+#include <features.h>
+#undef __GLIBC_USE_DEPRECATED_SCANF
+#define __GLIBC_USE_DEPRECATED_SCANF 1
+
 #include "libioP.h"
 #include "stdio.h"
 
diff --git a/misc/mntent_r.c b/misc/mntent_r.c
index 7a826586541..4e1a56620a5 100644
--- a/misc/mntent_r.c
+++ b/misc/mntent_r.c
@@ -26,6 +26,10 @@ 
 #define flockfile(s) _IO_flockfile (s)
 #define funlockfile(s) _IO_funlockfile (s)
 
+/* __REDIRECT isn't transitive.  */
+#undef sscanf
+#define sscanf __isoc99_sscanf
+
 #undef __setmntent
 #undef __endmntent
 #undef __getmntent_r
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 9dfc1153132..6b7f0172f08 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -61,6 +61,7 @@  tests := tstscanf test_rdwr test-popen tstgetln test-fseek \
 	 tst-printf-bz18872 tst-vfprintf-width-prec tst-fmemopen4 \
 	 tst-vfprintf-user-type \
 	 tst-vfprintf-mbs-prec \
+	 scanf14a scanf16a
 
 test-srcs = tst-unbputc tst-printf
 
@@ -136,13 +137,11 @@  CFLAGS-isoc99_scanf.c += -fexceptions
 CFLAGS-errlist.c += $(fno-unit-at-a-time)
 CFLAGS-siglist.c += $(fno-unit-at-a-time)
 
-# The following is a hack since we must compile scanf1{5,7}.c without any
-# GNU extension.  The latter are needed, though, when internal headers
-# are used.  So made sure we see the installed headers first.
-CFLAGS-scanf15.c += -I../libio -I../stdlib -I../wcsmbs -I../time -I../string \
-		   -I../wctype
-CFLAGS-scanf17.c += -I../libio -I../stdlib -I../wcsmbs -I../time -I../string \
-		   -I../wctype
+# scanf14a.c and scanf16a.c test a deprecated extension which is no
+# longer visible under most conformance levels; see the source files
+# for more detail.
+CFLAGS-scanf14a.c += -std=gnu89
+CFLAGS-scanf16a.c += -std=gnu89
 
 # tst-gets.c tests a deprecated function.
 CFLAGS-tst-gets.c += -Wno-deprecated-declarations
diff --git a/stdio-common/bug21.c b/stdio-common/bug21.c
index 7a8c6a35423..1f06c0dab4b 100644
--- a/stdio-common/bug21.c
+++ b/stdio-common/bug21.c
@@ -1,5 +1,4 @@ 
 #include <stdio.h>
-#include <libc-diag.h>
 
 static int
 do_test (void)
@@ -7,15 +6,7 @@  do_test (void)
   static const char buf[] = " ";
   char *str;
 
-  /* GCC in C99 mode treats %a as the C99 format expecting float *,
-     but glibc with _GNU_SOURCE treats %as as the GNU allocation
-     extension, so resulting in "warning: format '%a' expects argument
-     of type 'float *', but argument 3 has type 'char **'".  This
-     applies to the other %as, %aS and %a[] formats below as well.  */
-  DIAG_PUSH_NEEDS_COMMENT;
-  DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
-  int r = sscanf (buf, "%as", &str);
-  DIAG_POP_NEEDS_COMMENT;
+  int r = sscanf (buf, "%ms", &str);
   printf ("%d %p\n", r, str);
 
   return r != -1 || str != NULL;
diff --git a/stdio-common/fscanf.c b/stdio-common/fscanf.c
index b60a2a3b811..9f2a7cdef9f 100644
--- a/stdio-common/fscanf.c
+++ b/stdio-common/fscanf.c
@@ -15,6 +15,11 @@ 
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+/* This file defines one of the deprecated scanf variants.  */
+#include <features.h>
+#undef __GLIBC_USE_DEPRECATED_SCANF
+#define __GLIBC_USE_DEPRECATED_SCANF 1
+
 #include <libioP.h>
 #include <stdarg.h>
 #include <stdio.h>
diff --git a/stdio-common/isoc99_sscanf.c b/stdio-common/isoc99_sscanf.c
index 56a60a2c05e..d0f826a13e7 100644
--- a/stdio-common/isoc99_sscanf.c
+++ b/stdio-common/isoc99_sscanf.c
@@ -33,3 +33,4 @@  __isoc99_sscanf (const char *s, const char *format, ...)
 
   return done;
 }
+libc_hidden_def (__isoc99_sscanf)
diff --git a/stdio-common/scanf.c b/stdio-common/scanf.c
index e61b5f1ad33..5089e29b6bd 100644
--- a/stdio-common/scanf.c
+++ b/stdio-common/scanf.c
@@ -15,6 +15,11 @@ 
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+/* This file defines one of the deprecated scanf variants.  */
+#include <features.h>
+#undef __GLIBC_USE_DEPRECATED_SCANF
+#define __GLIBC_USE_DEPRECATED_SCANF 1
+
 #include <stdarg.h>
 #include <stdio.h>
 
diff --git a/stdio-common/scanf14.c b/stdio-common/scanf14.c
index 2bcd9c9893d..58727d720d6 100644
--- a/stdio-common/scanf14.c
+++ b/stdio-common/scanf14.c
@@ -2,7 +2,6 @@ 
 #include <stdlib.h>
 #include <string.h>
 #include <wchar.h>
-#include <libc-diag.h>
 
 #define FAIL() \
   do {							\
@@ -24,14 +23,7 @@  main (void)
     FAIL ();
   else if (f != 0.25 || memcmp (c, "s x", 3) != 0)
     FAIL ();
-  /* GCC in C99 mode treats %a as the C99 format expecting float *,
-     but glibc with _GNU_SOURCE treats %as as the GNU allocation
-     extension, so resulting in "warning: format '%a' expects argument
-     of type 'float *', but argument 3 has type 'char **'".  This
-     applies to the other %as, %aS and %a[] formats below as well.  */
-  DIAG_PUSH_NEEDS_COMMENT;
-  DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
-  if (sscanf (" 1.25s x", "%as%2c", &sp, c) != 2)
+  if (sscanf (" 1.25s x", "%ms%2c", &sp, c) != 2)
     FAIL ();
   else
     {
@@ -40,15 +32,11 @@  main (void)
       memset (sp, 'x', sizeof "1.25s");
       free (sp);
     }
-  DIAG_POP_NEEDS_COMMENT;
   if (sscanf (" 2.25s x", "%las%2c", &d, c) != 2)
     FAIL ();
   else if (d != 2.25 || memcmp (c, " x", 2) != 0)
     FAIL ();
-  /* See explanation above.  */
-  DIAG_PUSH_NEEDS_COMMENT;
-  DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
-  if (sscanf (" 3.25S x", "%4aS%3c", &lsp, c) != 2)
+  if (sscanf (" 3.25S x", "%4mS%3c", &lsp, c) != 2)
     FAIL ();
   else
     {
@@ -57,7 +45,7 @@  main (void)
       memset (lsp, 'x', sizeof L"3.25");
       free (lsp);
     }
-  if (sscanf ("4.25[0-9.] x", "%a[0-9.]%8c", &sp, c) != 2)
+  if (sscanf ("4.25[0-9.] x", "%m[0-9.]%8c", &sp, c) != 2)
     FAIL ();
   else
     {
@@ -66,7 +54,6 @@  main (void)
       memset (sp, 'x', sizeof "4.25");
       free (sp);
     }
-  DIAG_POP_NEEDS_COMMENT;
   if (sscanf ("5.25[0-9.] x", "%la[0-9.]%2c", &d, c) != 2)
     FAIL ();
   else if (d != 5.25 || memcmp (c, " x", 2) != 0)
@@ -95,10 +82,7 @@  main (void)
 	FAIL ();
       if (fseek (fp, 0, SEEK_SET) != 0)
 	FAIL ();
-      /* See explanation above.  */
-      DIAG_PUSH_NEEDS_COMMENT;
-      DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
-      if (fscanf (fp, "%as%2c", &sp, c) != 2)
+      if (fscanf (fp, "%ms%2c", &sp, c) != 2)
 	FAIL ();
       else
 	{
@@ -107,16 +91,12 @@  main (void)
 	  memset (sp, 'x', sizeof "1.25s");
 	  free (sp);
 	}
-      DIAG_POP_NEEDS_COMMENT;
 
       if (freopen (fname, "r", stdin) == NULL)
 	FAIL ();
       else
 	{
-	  /* See explanation above.  */
-	  DIAG_PUSH_NEEDS_COMMENT;
-	  DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wformat");
-	  if (scanf ("%as%2c", &sp, c) != 2)
+	  if (scanf ("%ms%2c", &sp, c) != 2)
 	    FAIL ();
 	  else
 	    {
@@ -125,7 +105,6 @@  main (void)
 	      memset (sp, 'x', sizeof "1.25s");
 	      free (sp);
 	    }
-	  DIAG_POP_NEEDS_COMMENT;
 	}
 
       fclose (fp);
diff --git a/stdio-common/scanf14a.c b/stdio-common/scanf14a.c
new file mode 100644
index 00000000000..082559371a7
--- /dev/null
+++ b/stdio-common/scanf14a.c
@@ -0,0 +1,133 @@ 
+/* This test exercises the deprecated GNU %as, %aS, and %a[...] scanf
+   modifiers, which are not available anymore under _GNU_SOURCE or any
+   conformance level including POSIX.1-2001.  */
+#undef _GNU_SOURCE
+#undef _DEFAULT_SOURCE
+#undef _XOPEN_SOURCE
+#undef _XOPEN_SOURCE_EXTENDED
+#undef _POSIX_C_SOURCE
+#undef _POSIX_SOURCE
+#undef _ISOC11_SOURCE
+#undef _ISOC99_SOURCE
+#undef __STRICT_ANSI__
+
+#define _POSIX_C_SOURCE 199506L
+#define _XOPEN_SOURCE 1          /* for _XOPEN_SOURCE_EXTENDED */
+#define _XOPEN_SOURCE_EXTENDED 1 /* for mkstemp */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#define FAIL() \
+  do {							\
+    result = 1;						\
+    printf ("test at line %d failed\n", __LINE__);	\
+  } while (0)
+
+int
+main (void)
+{
+  wchar_t *lsp;
+  char *sp;
+  float f;
+  double d;
+  char c[8];
+  int result = 0;
+
+  if (sscanf (" 0.25s x", "%e%3c", &f, c) != 2)
+    FAIL ();
+  else if (f != 0.25 || memcmp (c, "s x", 3) != 0)
+    FAIL ();
+  if (sscanf (" 1.25s x", "%as%2c", &sp, c) != 2)
+    FAIL ();
+  else
+    {
+      if (strcmp (sp, "1.25s") != 0 || memcmp (c, " x", 2) != 0)
+	FAIL ();
+      memset (sp, 'x', sizeof "1.25s");
+      free (sp);
+    }
+  if (sscanf (" 2.25s x", "%las%2c", &d, c) != 2)
+    FAIL ();
+  else if (d != 2.25 || memcmp (c, " x", 2) != 0)
+    FAIL ();
+  if (sscanf (" 3.25S x", "%4aS%3c", &lsp, c) != 2)
+    FAIL ();
+  else
+    {
+      if (wcscmp (lsp, L"3.25") != 0 || memcmp (c, "S x", 3) != 0)
+	FAIL ();
+      memset (lsp, 'x', sizeof L"3.25");
+      free (lsp);
+    }
+  if (sscanf ("4.25[0-9.] x", "%a[0-9.]%8c", &sp, c) != 2)
+    FAIL ();
+  else
+    {
+      if (strcmp (sp, "4.25") != 0 || memcmp (c, "[0-9.] x", 8) != 0)
+	FAIL ();
+      memset (sp, 'x', sizeof "4.25");
+      free (sp);
+    }
+  if (sscanf ("5.25[0-9.] x", "%la[0-9.]%2c", &d, c) != 2)
+    FAIL ();
+  else if (d != 5.25 || memcmp (c, " x", 2) != 0)
+    FAIL ();
+
+  const char *tmpdir = getenv ("TMPDIR");
+  if (tmpdir == NULL || tmpdir[0] == '\0')
+    tmpdir = "/tmp";
+
+  char fname[strlen (tmpdir) + sizeof "/tst-scanf14.XXXXXX"];
+  sprintf (fname, "%s/tst-scanf14.XXXXXX", tmpdir);
+  if (fname == NULL)
+    FAIL ();
+
+  /* Create a temporary file.   */
+  int fd = mkstemp (fname);
+  if (fd == -1)
+    FAIL ();
+
+  FILE *fp = fdopen (fd, "w+");
+  if (fp == NULL)
+    FAIL ();
+  else
+    {
+      if (fputs (" 1.25s x", fp) == EOF)
+	FAIL ();
+      if (fseek (fp, 0, SEEK_SET) != 0)
+	FAIL ();
+      if (fscanf (fp, "%as%2c", &sp, c) != 2)
+	FAIL ();
+      else
+	{
+	  if (strcmp (sp, "1.25s") != 0 || memcmp (c, " x", 2) != 0)
+	    FAIL ();
+	  memset (sp, 'x', sizeof "1.25s");
+	  free (sp);
+	}
+
+      if (freopen (fname, "r", stdin) == NULL)
+	FAIL ();
+      else
+	{
+	  if (scanf ("%as%2c", &sp, c) != 2)
+	    FAIL ();
+	  else
+	    {
+	      if (strcmp (sp, "1.25s") != 0 || memcmp (c, " x", 2) != 0)
+		FAIL ();
+	      memset (sp, 'x', sizeof "1.25s");
+	      free (sp);
+	    }
+	}
+
+      fclose (fp);
+    }
+
+  remove (fname);
+
+  return result;
+}
diff --git a/stdio-common/scanf15.c b/stdio-common/scanf15.c
index a3ab15dea22..0e536ffacd5 100644
--- a/stdio-common/scanf15.c
+++ b/stdio-common/scanf15.c
@@ -1,13 +1,3 @@ 
-#undef _GNU_SOURCE
-#define _XOPEN_SOURCE 600
-#undef _LIBC
-#undef _IO_MTSAFE_IO
-/* The following macro definitions are a hack.  They word around disabling
-   the GNU extension while still using a few internal headers.  */
-#define u_char unsigned char
-#define u_short unsigned short
-#define u_int unsigned int
-#define u_long unsigned long
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
diff --git a/stdio-common/scanf16.c b/stdio-common/scanf16.c
index 3e3cb417f21..3394cdc288a 100644
--- a/stdio-common/scanf16.c
+++ b/stdio-common/scanf16.c
@@ -10,7 +10,7 @@ 
     printf ("test at line %d failed\n", __LINE__);	\
   } while (0)
 
-static int
+static int __attribute__ ((format (scanf, 2, 3)))
 xsscanf (const char *str, const char *fmt, ...)
 {
   va_list ap;
@@ -20,7 +20,7 @@  xsscanf (const char *str, const char *fmt, ...)
   return ret;
 }
 
-static int
+static int __attribute__ ((format (scanf, 1, 2)))
 xscanf (const char *fmt, ...)
 {
   va_list ap;
@@ -30,7 +30,7 @@  xscanf (const char *fmt, ...)
   return ret;
 }
 
-static int
+static int __attribute__ ((format (scanf, 2, 3)))
 xfscanf (FILE *f, const char *fmt, ...)
 {
   va_list ap;
@@ -54,7 +54,7 @@  main (void)
     FAIL ();
   else if (f != 0.25 || memcmp (c, "s x", 3) != 0)
     FAIL ();
-  if (xsscanf (" 1.25s x", "%as%2c", &sp, c) != 2)
+  if (xsscanf (" 1.25s x", "%ms%2c", &sp, c) != 2)
     FAIL ();
   else
     {
@@ -67,7 +67,7 @@  main (void)
     FAIL ();
   else if (d != 2.25 || memcmp (c, " x", 2) != 0)
     FAIL ();
-  if (xsscanf (" 3.25S x", "%4aS%3c", &lsp, c) != 2)
+  if (xsscanf (" 3.25S x", "%4mS%3c", &lsp, c) != 2)
     FAIL ();
   else
     {
@@ -76,7 +76,7 @@  main (void)
       memset (lsp, 'x', sizeof L"3.25");
       free (lsp);
     }
-  if (xsscanf ("4.25[0-9.] x", "%a[0-9.]%8c", &sp, c) != 2)
+  if (xsscanf ("4.25[0-9.] x", "%m[0-9.]%8c", &sp, c) != 2)
     FAIL ();
   else
     {
@@ -113,7 +113,7 @@  main (void)
 	FAIL ();
       if (fseek (fp, 0, SEEK_SET) != 0)
 	FAIL ();
-      if (xfscanf (fp, "%as%2c", &sp, c) != 2)
+      if (xfscanf (fp, "%ms%2c", &sp, c) != 2)
 	FAIL ();
       else
 	{
@@ -127,7 +127,7 @@  main (void)
 	FAIL ();
       else
 	{
-	  if (xscanf ("%as%2c", &sp, c) != 2)
+	  if (xscanf ("%ms%2c", &sp, c) != 2)
 	    FAIL ();
 	  else
 	    {
diff --git a/stdio-common/scanf16a.c b/stdio-common/scanf16a.c
new file mode 100644
index 00000000000..9b36f127b26
--- /dev/null
+++ b/stdio-common/scanf16a.c
@@ -0,0 +1,165 @@ 
+/* This test exercises the deprecated GNU %as, %aS, and %a[...] scanf
+   modifiers, which are not available anymore under _GNU_SOURCE or any
+   conformance level including POSIX.1-2001.  */
+#undef _GNU_SOURCE
+#undef _DEFAULT_SOURCE
+#undef _XOPEN_SOURCE
+#undef _XOPEN_SOURCE_EXTENDED
+#undef _POSIX_C_SOURCE
+#undef _POSIX_SOURCE
+#undef _ISOC11_SOURCE
+#undef _ISOC99_SOURCE
+#undef __STRICT_ANSI__
+
+#define _POSIX_C_SOURCE 199506L
+#define _XOPEN_SOURCE 1          /* for _XOPEN_SOURCE_EXTENDED */
+#define _XOPEN_SOURCE_EXTENDED 1 /* for mkstemp */
+#define _ISOC99_SOURCE 1         /* for v*scanf */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#define FAIL() \
+  do {							\
+    result = 1;						\
+    printf ("test at line %d failed\n", __LINE__);	\
+  } while (0)
+
+static int __attribute__ ((format (scanf, 2, 3)))
+xsscanf (const char *str, const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  int ret = vsscanf (str, fmt, ap);
+  va_end (ap);
+  return ret;
+}
+
+static int __attribute__ ((format (scanf, 1, 2)))
+xscanf (const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  int ret = vscanf (fmt, ap);
+  va_end (ap);
+  return ret;
+}
+
+static int __attribute__ ((format (scanf, 2, 3)))
+xfscanf (FILE *f, const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  int ret = vfscanf (f, fmt, ap);
+  va_end (ap);
+  return ret;
+}
+
+int
+main (void)
+{
+  wchar_t *lsp;
+  char *sp;
+  float f;
+  double d;
+  char c[8];
+  int result = 0;
+
+  if (xsscanf (" 0.25s x", "%e%3c", &f, c) != 2)
+    FAIL ();
+  else if (f != 0.25 || memcmp (c, "s x", 3) != 0)
+    FAIL ();
+  if (xsscanf (" 1.25s x", "%as%2c", &sp, c) != 2)
+    FAIL ();
+  else
+    {
+      if (strcmp (sp, "1.25s") != 0 || memcmp (c, " x", 2) != 0)
+	FAIL ();
+      memset (sp, 'x', sizeof "1.25s");
+      free (sp);
+    }
+  if (xsscanf (" 2.25s x", "%las%2c", &d, c) != 2)
+    FAIL ();
+  else if (d != 2.25 || memcmp (c, " x", 2) != 0)
+    FAIL ();
+  if (xsscanf (" 3.25S x", "%4aS%3c", &lsp, c) != 2)
+    FAIL ();
+  else
+    {
+      if (wcscmp (lsp, L"3.25") != 0 || memcmp (c, "S x", 3) != 0)
+	FAIL ();
+      memset (lsp, 'x', sizeof L"3.25");
+      free (lsp);
+    }
+  if (xsscanf ("4.25[0-9.] x", "%a[0-9.]%8c", &sp, c) != 2)
+    FAIL ();
+  else
+    {
+      if (strcmp (sp, "4.25") != 0 || memcmp (c, "[0-9.] x", 8) != 0)
+	FAIL ();
+      memset (sp, 'x', sizeof "4.25");
+      free (sp);
+    }
+  if (xsscanf ("5.25[0-9.] x", "%la[0-9.]%2c", &d, c) != 2)
+    FAIL ();
+  else if (d != 5.25 || memcmp (c, " x", 2) != 0)
+    FAIL ();
+
+  const char *tmpdir = getenv ("TMPDIR");
+  if (tmpdir == NULL || tmpdir[0] == '\0')
+    tmpdir = "/tmp";
+
+  char fname[strlen (tmpdir) + sizeof "/tst-scanf16.XXXXXX"];
+  sprintf (fname, "%s/tst-scanf16.XXXXXX", tmpdir);
+  if (fname == NULL)
+    FAIL ();
+
+  /* Create a temporary file.   */
+  int fd = mkstemp (fname);
+  if (fd == -1)
+    FAIL ();
+
+  FILE *fp = fdopen (fd, "w+");
+  if (fp == NULL)
+    FAIL ();
+  else
+    {
+      if (fputs (" 1.25s x", fp) == EOF)
+	FAIL ();
+      if (fseek (fp, 0, SEEK_SET) != 0)
+	FAIL ();
+      if (xfscanf (fp, "%as%2c", &sp, c) != 2)
+	FAIL ();
+      else
+	{
+	  if (strcmp (sp, "1.25s") != 0 || memcmp (c, " x", 2) != 0)
+	    FAIL ();
+	  memset (sp, 'x', sizeof "1.25s");
+	  free (sp);
+	}
+
+      if (freopen (fname, "r", stdin) == NULL)
+	FAIL ();
+      else
+	{
+	  if (xscanf ("%as%2c", &sp, c) != 2)
+	    FAIL ();
+	  else
+	    {
+	      if (strcmp (sp, "1.25s") != 0 || memcmp (c, " x", 2) != 0)
+		FAIL ();
+	      memset (sp, 'x', sizeof "1.25s");
+	      free (sp);
+	    }
+	}
+
+      fclose (fp);
+    }
+
+  remove (fname);
+
+  return result;
+}
diff --git a/stdio-common/scanf17.c b/stdio-common/scanf17.c
index b6c0e63ab02..d5c7983dcb2 100644
--- a/stdio-common/scanf17.c
+++ b/stdio-common/scanf17.c
@@ -1,13 +1,3 @@ 
-#undef _GNU_SOURCE
-#define _XOPEN_SOURCE 600
-#undef _LIBC
-#undef _IO_MTSAFE_IO
-/* The following macro definitions are a hack.  They word around disabling
-   the GNU extension while still using a few internal headers.  */
-#define u_char unsigned char
-#define u_short unsigned short
-#define u_int unsigned int
-#define u_long unsigned long
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
diff --git a/stdio-common/sscanf.c b/stdio-common/sscanf.c
index 88cd641798b..7d08752d46c 100644
--- a/stdio-common/sscanf.c
+++ b/stdio-common/sscanf.c
@@ -15,6 +15,11 @@ 
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+/* This file defines one of the deprecated scanf variants.  */
+#include <features.h>
+#undef __GLIBC_USE_DEPRECATED_SCANF
+#define __GLIBC_USE_DEPRECATED_SCANF 1
+
 #include <stdarg.h>
 #include <stdio.h>
 #include <libioP.h>
@@ -34,7 +39,6 @@  __sscanf (const char *s, const char *format, ...)
 
   return done;
 }
-ldbl_hidden_def (__sscanf, sscanf)
 ldbl_strong_alias (__sscanf, sscanf)
 #undef _IO_sscanf
 /* This is for libg++.  */
diff --git a/stdio-common/vfscanf.c b/stdio-common/vfscanf.c
index 099dfeac9e6..4f393e4c332 100644
--- a/stdio-common/vfscanf.c
+++ b/stdio-common/vfscanf.c
@@ -15,6 +15,11 @@ 
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+/* This file defines one of the deprecated scanf variants.  */
+#include <features.h>
+#undef __GLIBC_USE_DEPRECATED_SCANF
+#define __GLIBC_USE_DEPRECATED_SCANF 1
+
 #include <assert.h>
 #include <errno.h>
 #include <limits.h>
diff --git a/time/tzset.c b/time/tzset.c
index b51786704ae..80b2e30c207 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -27,6 +27,10 @@ 
 
 #include <timezone/tzfile.h>
 
+/* __REDIRECT isn't transitive.  */
+#undef sscanf
+#define sscanf __isoc99_sscanf
+
 #define SECSPERDAY 86400
 
 char *__tzname[2] = { (char *) "GMT", (char *) "GMT" };