Signal 0 on FreeBSD 10

Bruce Evans brde at optusnet.com.au
Tue Mar 11 20:57:58 UTC 2014


[List changed from developers to standards]

On Wed, 12 Mar 2014, Bruce Evans wrote:

> Some wording from POSIX.1-2001-draft7:
>
> % 3543 DESCRIPTION
> % 3544 CX           The functionality described on this reference page is 
> aligned with the ISO C standard. Any
> % 3545              conflict between the requirements described here and the 
> ISO C standard is unintentional. This
> % 3546              volume of IEEE Std 1003.1-200x defers to the ISO C 
> standard.
>
> Since the specification for fclose()ing conflicts, this paragraph voids the
> specification for fclose()ing.
>
> % 3547              The abort( ) function shall cause abnormal process 
> termination to occur, unless the signal
> % 3548              SIGABRT is being caught and the signal handler does not 
> return.
>
> Strangely, POSIX turns off the requirement for abort() to fclose() in the
> one case where C99 allows it to work.  Perhaps I am reading something
> backwards.
>
> % 3549 CX           The abnormal termination processing shall include at 
> least the effect of fclose( ) on all open
> % 3550              streams and the default actions defined for SIGABRT.
> % 3551 XSI          On XSI-conformant systems, in addition the abnormal 
> termination processing shall include the
> % 3552              effect of fclose( ) on message catalog descriptors.
>
> The part that specifies undefined behaviour.  It is only undefined when
> abort() is called from a signal handler other than after certain voluntary
> signals, of course.
>
> Perhaps this is fixed in newer versions of POSIX, but it is impossible to
> fix the historical mistake of fclose()ing in signal handlers.  All that
> can be required is to attempt to do safe parts of fclose() on streams
> which can be safely located by the implementation.  An average
> implementation can barely locate stderr safely, but doesn't know that
> this is safe so it can't do anything.

This is indeed sort of fixed in newer versions.  The changes are even
documented in the changes section.
- abort() is now in the async-signal-safe list
- "shall include at least the effect of fclose( )" is changed to
   "_may_ include an _attempt_ to effect fclose( )".
- the extras for XSI are removed (they were null because message catalogs
   are read-only)
So the historical behaviour is permitted (except for undefined behaviour
in it), but it is fully optional so applications can no longer depend
on the implementation making best efforts to do it.

The fflush() in FreeBSD was new in 4.4BSD.  It was not in FreeBSD-1.  So
it is perhaps not too late to remove it.

% --- /c/src1/lib/libc/stdlib/abort.c	Sun Jun 13 00:56:10 1993
% +++ /usr/src/lib/libc/stdlib/abort.c	Sat Aug 23 14:16:03 2003
% @@ -1,5 +1,5 @@
%  /*
% - * Copyright (c) 1985 Regents of the University of California.
% - * All rights reserved.
% + * Copyright (c) 1985, 1993
% + *	The Regents of the University of California.  All rights reserved.
%   *
%   * Redistribution and use in source and binary forms, with or without
% @@ -33,33 +33,51 @@
% 
%  #if defined(LIBC_SCCS) && !defined(lint)
% -static char sccsid[] = "@(#)abort.c	5.11 (Berkeley) 2/23/91";
% +static char sccsid[] = "@(#)abort.c	8.1 (Berkeley) 6/4/93";
%  #endif /* LIBC_SCCS and not lint */
% +#include <sys/cdefs.h>
% +__FBSDID("$FreeBSD: src/lib/libc/stdlib/abort.c,v 1.9 2003/08/16 11:43:57 davidxu Exp $");
% 
% -#include <sys/signal.h>
% +#include "namespace.h"
% +#include <signal.h>
%  #include <stdlib.h>
%  #include <stddef.h>
%  #include <unistd.h>
% +#include <pthread.h>
% +#include "un-namespace.h"
% +
% +void (*__cleanup)();
% 
%  void
%  abort()
%  {
% -	sigset_t mask;
% +	struct sigaction act;
% +
% +	/*
% +	 * POSIX requires we flush stdio buffers on abort.
% +	 * XXX ISO C requires that abort() be async-signal-safe.
% +	 */
% +	if (__cleanup)
% +		(*__cleanup)();

POSIX mistakes here.

% 
% -	sigfillset(&mask);
% +	sigfillset(&act.sa_mask);
%  	/*
% -	 * don't block SIGABRT to give any handler a chance; we ignore
% -	 * any errors -- X311J doesn't allow abort to return anyway.
% +	 * Don't block SIGABRT to give any handler a chance; we ignore
% +	 * any errors -- ISO C doesn't allow abort to return anyway.
%  	 */
% -	sigdelset(&mask, SIGABRT);
% -	(void)sigprocmask(SIG_SETMASK, &mask, (sigset_t *)NULL);
% -	(void)kill(getpid(), SIGABRT);
% +	sigdelset(&act.sa_mask, SIGABRT);
% +	(void)_sigprocmask(SIG_SETMASK, &act.sa_mask, NULL);
% +	(void)raise(SIGABRT);
% 
%  	/*
% -	 * if SIGABRT ignored, or caught and the handler returns, do
% +	 * If SIGABRT was ignored, or caught and the handler returns, do
%  	 * it again, only harder.
%  	 */
% -	(void)signal(SIGABRT, SIG_DFL);
% -	(void)sigprocmask(SIG_SETMASK, &mask, (sigset_t *)NULL);
% -	(void)kill(getpid(), SIGABRT);
% +	act.sa_handler = SIG_DFL;
% +	act.sa_flags = 0;
% +	sigfillset(&act.sa_mask);
% +	(void)_sigaction(SIGABRT, &act, NULL);
% +	sigdelset(&act.sa_mask, SIGABRT);
% +	(void)_sigprocmask(SIG_SETMASK, &act.sa_mask, NULL);
% +	(void)raise(SIGABRT);
%  	exit(1);
%  }

One of the URLs pointed to by eadler points out that the threaded case
might be less async-signal-safe than the non-threaded case, since
threading probably needs locks which may deadlock.  So the above
__cleanup call is now less safe than when it was added, since there
was no threaded case then but there is now.

I don't really understand locking in stdio.  fflush() locks the
file, and some places use sflush_locked(), but __cleanup uses
unlocked __sflush().  This uses of __sflush() predates the existence
of locking.  Perhaps it was left unlocked to avoid deadlock.  I
think it just ignores locks, and thus is broken even for non-async
signal handling, not to mention other uses like non-async calls to
exit(), since it races with other threads then.

The POSIX list has reports of interesting deadlocks instead of races
near exit() when everything uses locks.  In the threaded case, even
exiting is difficult, since exit() may block forever on locks held
by other threads.  Apparently it is necessary to make all the other
threads terminate cleanly before exiting.  Just killing them doesn't
work since it might leave locks held.  Terminating the other threads
cleanly in abort() so that its fclose()ing doesn't block is even
harder, but abort() has the advantage() that its fclose()s are
optional.  It should skip the locked streams.

Bruce


More information about the freebsd-standards mailing list