E-MailRelay
gcleanup_unix.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gcleanup_unix.cpp
19///
20
21#include "gdef.h"
22#include "gcleanup.h"
23#include "gprocess.h"
24#include "groot.h"
25#include "glog.h"
26#include <cstring> // ::strdup()
27#include <csignal> // ::sigaction() etc
28#include <array>
29
30extern "C"
31{
32 void gcleanup_handler( int signum ) ;
33 using Handler = void (*)(int) ;
34}
35
36namespace G
37{
38 class CleanupImp ;
39}
40
41//| \class G::CleanupImp
42/// A static implementation class used by G::Cleanup.
43///
44class G::CleanupImp
45{
46public:
47 static void add( bool (*fn)(SignalSafe,const char*) , const char * ) ;
48 // Adds a cleanup function.
49
50 static void installDefault( const SignalSafe & , int ) ;
51 // Installs the SIG_DFL signal handler for the given signal.
52
53 static void installDefault( int ) ;
54 // Installs the SIG_DFL signal handler for the given signal.
55
56 static void installIgnore( int ) ;
57 // Installs the SIG_IGN signal handler for the given signal.
58
59 static void callHandlers() ;
60 // Calls all the cleanup functions. Any that fail are automatically
61 // retried with Root::atExit().
62
63 static bool callHandlersOnce( SignalSafe ) ;
64 // Calls all the cleanup functions.
65
66 static void atexit( bool active ) ;
67 // Registers callHandlers() with atexit(3) if the active parameter is true.
68
69 static void block() noexcept ;
70 // Blocks signals until released.
71
72 static void release() noexcept ;
73 // Releases blocked signals.
74
75 static const char * strdup_ignore_leaks( const char * p ) ;
76 // A strdup() function.
77
78private:
79 struct Link /// A private linked-list structure used by G::CleanupImp.
80 {
81 bool (*fn)(SignalSafe,const char*) ;
82 const char * arg ;
83 Link * next ;
84 bool done ;
85 } ;
86
87private:
88 static void init() ;
89 static void install( int , Handler , bool ) ;
90 static void installHandler( int ) ;
91 static bool ignored( int ) ;
92 static void atexitHandler() ;
93 static Link * new_link_ignore_leak() ;
94
95private:
96 static Link * m_head ;
97 static Link * m_tail ;
98 static bool m_atexit_active ;
99 static bool m_atexit_installed ;
100 static std::array<int,4U> m_signals ;
101} ;
102
103std::array<int,4U> G::CleanupImp::m_signals = {{ SIGTERM , SIGINT , SIGHUP , SIGQUIT }} ;
104G::CleanupImp::Link * G::CleanupImp::m_head = nullptr ;
105G::CleanupImp::Link * G::CleanupImp::m_tail = nullptr ;
106bool G::CleanupImp::m_atexit_installed = false ;
107bool G::CleanupImp::m_atexit_active = false ;
108
109// ===
110
112{
113 CleanupImp::installIgnore( SIGPIPE ) ;
114}
115
116void G::Cleanup::add( bool (*fn)(SignalSafe,const char*) , const char * arg )
117{
118 CleanupImp::add( fn , arg ) ;
119}
120
121#ifndef G_LIB_SMALL
122void G::Cleanup::atexit( bool active )
123{
124 CleanupImp::atexit( active ) ;
125}
126#endif
127
128void G::Cleanup::block() noexcept
129{
130 CleanupImp::block() ;
131}
132
133void G::Cleanup::release() noexcept
134{
135 CleanupImp::release() ;
136}
137
138#ifndef G_LIB_SMALL
139const char * G::Cleanup::strdup( const char * p )
140{
141 return CleanupImp::strdup_ignore_leaks( p ) ;
142}
143#endif
144
145const char * G::Cleanup::strdup( const std::string & s )
146{
147 return CleanupImp::strdup_ignore_leaks( s.c_str() ) ;
148}
149
150// ===
151
152void G::CleanupImp::init()
153{
154 // install our meta-handler for signals that normally terminate the process,
155 // except for sigpipe which we ignore
156 //
157 installIgnore( SIGPIPE ) ;
158 for( int s : m_signals )
159 installHandler( s ) ;
160}
161
162void G::CleanupImp::add( bool (*fn)(SignalSafe,const char*) , const char * arg )
163{
164 Link * p = new_link_ignore_leak() ;
165 p->fn = fn ;
166 p->arg = arg ;
167 p->next = nullptr ;
168 p->done = false ;
169
170 Cleanup::Block block ;
171 if( m_head == nullptr ) init() ;
172 if( m_tail != nullptr ) m_tail->next = p ;
173 m_tail = p ;
174 if( m_head == nullptr ) m_head = p ;
175}
176
177G::CleanupImp::Link * G::CleanupImp::new_link_ignore_leak()
178{
179 return new Link ; // ignore leak
180}
181
182void G::CleanupImp::installHandler( int signum )
183{
184 if( ignored(signum) )
185 G_DEBUG( "G::CleanupImp::installHandler: signal " << signum << " is ignored" ) ;
186 else
187 install( signum , gcleanup_handler , true ) ;
188}
189
190bool G::CleanupImp::ignored( int signum )
191{
192 struct ::sigaction action {} ;
193 if( ::sigaction( signum , nullptr , &action ) != 0 )
194 throw Cleanup::Error( "sigaction" ) ;
195 return action.sa_handler == SIG_IGN ; // NOLINT
196}
197
198#ifndef G_LIB_SMALL
199void G::CleanupImp::installDefault( int signum )
200{
201 install( signum , SIG_DFL , true ) ;
202}
203#endif
204
205#ifndef G_LIB_SMALL
206void G::CleanupImp::installDefault( const G::SignalSafe & , int signum )
207{
208 install( signum , SIG_DFL , false ) ;
209}
210#endif
211
212void G::CleanupImp::installIgnore( int signum )
213{
214 install( signum , SIG_IGN , true ) ; // NOLINT
215}
216
217void G::CleanupImp::install( int signum , Handler fn , bool do_throw )
218{
219 // install the given handler, or the system default if null
220 struct ::sigaction action {} ;
221 action.sa_handler = fn ;
222 if( ::sigaction( signum , &action , nullptr ) != 0 && do_throw )
223 throw Cleanup::Error( "sigaction" ) ;
224}
225
226void G::CleanupImp::atexit( bool active )
227{
228 if( active && !m_atexit_installed )
229 {
230 m_atexit_installed = true ;
231 ::atexit( atexitHandler ) ;
232 }
233 m_atexit_active = active ;
234}
235
236void G::CleanupImp::atexitHandler()
237{
238 if( m_atexit_active )
239 callHandlers() ;
240}
241
242void G::CleanupImp::callHandlers()
243{
244 if( !callHandlersOnce( SignalSafe() ) )
245 {
246 Root::atExit( SignalSafe() ) ;
247 callHandlersOnce( SignalSafe() ) ;
248 }
249}
250
251bool G::CleanupImp::callHandlersOnce( SignalSafe )
252{
253 bool all_ok = true ;
254 for( Link * p = m_head ; p != nullptr ; p = p->next )
255 {
256 try
257 {
258 if( !p->done && (*(p->fn))(SignalSafe(),p->arg) )
259 p->done = true ;
260 else
261 all_ok = false ;
262 }
263 catch(...)
264 {
265 }
266 }
267 return all_ok ;
268}
269
270extern "C" void gcleanup_handler( int signum )
271{
272 // call the registered handler(s) and exit
273 try
274 {
275 G::CleanupImp::callHandlers() ;
276 std::_Exit( signum + 128 ) ;
277 }
278 catch(...)
279 {
280 }
281}
282
283void G::CleanupImp::block() noexcept
284{
285 sigset_t set ;
286 sigemptyset( &set ) ;
287 for( int s : m_signals )
288 {
289 sigaddset( &set , s ) ;
290 }
291 gdef_pthread_sigmask( SIG_BLOCK , &set , nullptr ) ; // gdef.h
292}
293
294void G::CleanupImp::release() noexcept
295{
296 sigset_t emptyset ;
297 sigemptyset( &emptyset ) ;
298 sigset_t set ;
299 sigemptyset( &set ) ;
300 gdef_pthread_sigmask( SIG_BLOCK , &emptyset , &set ) ;
301 for( int s : m_signals )
302 {
303 sigdelset( &set , s ) ;
304 }
305 gdef_pthread_sigmask( SIG_SETMASK , &set , nullptr ) ;
306}
307
308const char * G::CleanupImp::strdup_ignore_leaks( const char * p )
309{
310 return ::strdup( p ) ; // NOLINT
311}
312
static const char * strdup(const char *)
A strdup() function that makes it clear in the stack trace that leaks are expected.
static void atexit(bool active=true)
Ensures that the cleanup functions are also called via atexit(), in addition to abnormal-termination ...
static void release() noexcept
Releases block()ed signals.
static void add(bool(*fn)(SignalSafe, const char *), const char *arg)
Adds the given handler to the list of handlers that are to be called when the process terminates abno...
static void block() noexcept
Temporarily blocks signals until release()d.
static void init()
An optional early-initialisation function. May be called more than once.
static void atExit() noexcept
Re-acquires special privileges just before process exit.
Definition: groot.cpp:68
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:37
Low-level classes.
Definition: garg.h:30