E-MailRelay
gcleanup_unix.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 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 "gassert.h"
27#include <limits>
28#include <new> // operator new()
29#include <cstring> // std::memcpy()
30#include <csignal> // ::sigaction() etc
31#include <array>
32
33extern "C"
34{
35 void gcleanup_handler( int signum ) ;
36 using Handler = void (*)(int) ;
37}
38
39namespace G
40{
41 class CleanupImp
42 {
43 public:
44 using Arg = Cleanup::Arg ;
45 struct Link
46 {
47 Cleanup::Fn fn ;
48 Arg arg ;
49 Link * next ;
50 bool done ;
51 } ;
52
53 static void add( Cleanup::Fn , Arg ) ;
54 static void installDefault( const SignalSafe & , int ) noexcept ;
55 static void installDefault( int ) ;
56 static void installIgnore( int ) ;
57 static void callHandlers() noexcept ;
58 static bool callHandlersOnce( SignalSafe ) noexcept ;
59 static void atexit( bool active ) ;
60 static void block() noexcept ;
61 static void release() noexcept ;
62 static void init() ;
63 static void install( int , Handler ) ;
64 static void install( int , Handler , std::nothrow_t ) noexcept ;
65 static void installHandler( int ) ;
66 static bool ignored( int ) ;
67 static void atexitHandler() noexcept ;
68 static Arg duplicate( const char * , std::size_t , bool = false ) ;
69 static Link * new_link_ignore_leak() ;
70 static char * new_arg_ignore_leak( std::size_t ) ;
71
72 private:
73 static Link * m_head ;
74 static Link * m_tail ;
75 static bool m_atexit_active ;
76 static bool m_atexit_installed ;
77 static std::array<int,4U> m_signals ;
78 } ;
79}
80
81G::CleanupImp::Link * G::CleanupImp::m_head = nullptr ;
82G::CleanupImp::Link * G::CleanupImp::m_tail = nullptr ;
83bool G::CleanupImp::m_atexit_active = false ;
84bool G::CleanupImp::m_atexit_installed = false ;
85std::array<int,4U> G::CleanupImp::m_signals = {{ SIGTERM , SIGINT , SIGHUP , SIGQUIT }} ;
86
87// ===
88
90{
91 CleanupImp::installIgnore( SIGPIPE ) ;
92}
93
94void G::Cleanup::add( Fn fn , Arg arg )
95{
96 CleanupImp::add( fn , arg ) ;
97}
98
99#ifndef G_LIB_SMALL
100void G::Cleanup::atexit( bool active )
101{
102 CleanupImp::atexit( active ) ;
103}
104#endif
105
106void G::Cleanup::block() noexcept
107{
108 CleanupImp::block() ;
109}
110
111void G::Cleanup::release() noexcept
112{
113 CleanupImp::release() ;
114}
115
116#ifndef G_LIB_SMALL
118{
119 return CleanupImp::duplicate( p?p:"" , p?std::strlen(p):0U ) ;
120}
121#endif
122
123#ifndef G_LIB_SMALL
124G::Cleanup::Arg G::Cleanup::arg( const std::string & s )
125{
126 return CleanupImp::duplicate( s.data() , s.size() ) ;
127}
128#endif
129
131{
132 std::string s = p.str() ;
133 return CleanupImp::duplicate( s.data() , s.size() , true ) ;
134}
135
136#ifndef G_LIB_SMALL
138{
139 return CleanupImp::duplicate( nullptr , 0U ) ;
140}
141#endif
142
143// ==
144
145#ifndef G_LIB_SMALL
146bool G::Cleanup::Arg::isPath() const noexcept
147{
148 return m_is_path ;
149}
150#endif
151
152const char * G::Cleanup::Arg::str() const noexcept
153{
154 return m_ptr ;
155}
156
157// ===
158
159void G::CleanupImp::init()
160{
161 // install our meta-handler for signals that normally terminate the process,
162 // except for sigpipe which we ignore
163 //
164 installIgnore( SIGPIPE ) ;
165 for( int s : m_signals )
166 installHandler( s ) ;
167}
168
169void G::CleanupImp::add( Cleanup::Fn fn , Arg arg )
170{
171 // simple c-style data structures (with leaks) to avoid static destructors
172
173 Link * p = new_link_ignore_leak() ;
174 p->fn = fn ;
175 p->arg = arg ;
176 p->next = nullptr ;
177 p->done = false ;
178
179 Cleanup::Block block ;
180 if( m_head == nullptr ) init() ;
181 if( m_tail != nullptr ) m_tail->next = p ;
182 m_tail = p ;
183 if( m_head == nullptr ) m_head = p ;
184}
185
186G::CleanupImp::Link * G::CleanupImp::new_link_ignore_leak()
187{
188 return new Link ; // NOLINT // ignore leak
189}
190
191char * G::CleanupImp::new_arg_ignore_leak( std::size_t n )
192{
193 return static_cast<char*>( operator new(n) ) ; // NOLINT // ignore leak
194}
195
196G::Cleanup::Arg G::CleanupImp::duplicate( const char * p , std::size_t n , bool is_path )
197{
198 G_ASSERT( (n+std::size_t(1U)) > n ) ;
199 if( (n+std::size_t(1U)) <= n )
200 throw Cleanup::Error( "numeric overflow" ) ;
201
202 char * pp = new_arg_ignore_leak( n+1U ) ;
203 if( p && n ) std::memcpy( pp , p , n ) ;
204 pp[n] = '\0' ;
205
206 Arg a ;
207 a.m_ptr = pp ; // friend
208 a.m_is_path = is_path ;
209 return a ;
210}
211
212void G::CleanupImp::installHandler( int signum )
213{
214 if( ignored(signum) )
215 G_DEBUG( "G::CleanupImp::installHandler: signal " << signum << " is ignored" ) ;
216 else
217 install( signum , gcleanup_handler ) ;
218}
219
220bool G::CleanupImp::ignored( int signum )
221{
222 struct ::sigaction action {} ;
223 if( ::sigaction( signum , nullptr , &action ) != 0 )
224 throw Cleanup::Error( "sigaction" ) ;
225 return action.sa_handler == SIG_IGN ; // NOLINT macro shenanigans
226}
227
228#ifndef G_LIB_SMALL
229void G::CleanupImp::installDefault( int signum )
230{
231 install( signum , SIG_DFL ) ;
232}
233#endif
234
235#ifndef G_LIB_SMALL
236void G::CleanupImp::installDefault( const G::SignalSafe & , int signum ) noexcept
237{
238 install( signum , SIG_DFL , std::nothrow ) ;
239}
240#endif
241
242void G::CleanupImp::installIgnore( int signum )
243{
244 install( signum , SIG_IGN ) ; // NOLINT macro shenanigans
245}
246
247void G::CleanupImp::install( int signum , Handler fn )
248{
249 // install the given handler, or the system default if null
250 struct ::sigaction action {} ;
251 action.sa_handler = fn ;
252 if( ::sigaction( signum , &action , nullptr ) != 0 )
253 throw Cleanup::Error( "sigaction" ) ;
254}
255
256void G::CleanupImp::install( int signum , Handler fn , std::nothrow_t ) noexcept
257{
258 struct ::sigaction action {} ;
259 action.sa_handler = fn ;
260 ::sigaction( signum , &action , nullptr ) ;
261}
262
263void G::CleanupImp::atexit( bool active )
264{
265 if( active && !m_atexit_installed )
266 {
267 m_atexit_installed = true ;
268 ::atexit( atexitHandler ) ; // NOLINT
269 }
270 m_atexit_active = active ;
271}
272
273void G::CleanupImp::atexitHandler() noexcept
274{
275 if( m_atexit_active )
276 callHandlers() ;
277}
278
279void G::CleanupImp::callHandlers() noexcept
280{
281 if( !callHandlersOnce( SignalSafe() ) )
282 {
283 Root::atExit( SignalSafe() ) ;
284 callHandlersOnce( SignalSafe() ) ;
285 }
286}
287
288bool G::CleanupImp::callHandlersOnce( SignalSafe ) noexcept
289{
290 bool all_ok = true ;
291 for( Link * p = m_head ; p != nullptr ; p = p->next )
292 {
293 try
294 {
295 if( !p->done && (*(p->fn))(p->arg) )
296 p->done = true ;
297 else
298 all_ok = false ;
299 }
300 catch(...)
301 {
302 }
303 }
304 return all_ok ;
305}
306
307extern "C" void gcleanup_handler( int signum )
308{
309 // call the registered handler(s) and exit
310 static_assert( noexcept(G::CleanupImp::callHandlers()) , "" ) ;
311 G::CleanupImp::callHandlers() ;
312 std::_Exit( signum + 128 ) ;
313}
314
315void G::CleanupImp::block() noexcept
316{
317 sigset_t set ;
318 sigemptyset( &set ) ;
319 for( int s : m_signals )
320 {
321 sigaddset( &set , s ) ;
322 }
323 gdef_pthread_sigmask( SIG_BLOCK , &set , nullptr ) ; // gdef.h
324}
325
326void G::CleanupImp::release() noexcept
327{
328 sigset_t emptyset ;
329 sigemptyset( &emptyset ) ;
330 sigset_t set ;
331 sigemptyset( &set ) ;
332 gdef_pthread_sigmask( SIG_BLOCK , &emptyset , &set ) ;
333 for( int s : m_signals )
334 {
335 sigdelset( &set , s ) ;
336 }
337 gdef_pthread_sigmask( SIG_SETMASK , &set , nullptr ) ;
338}
339
static Arg arg(const char *)
Duplicates a c-string for add().
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 block() noexcept
Temporarily blocks signals until release()d.
static void init()
An optional early-initialisation function. May be called more than once.
static void add(Fn, Arg arg)
Adds the given handler to the list of handlers that are to be called when the process terminates abno...
A Path object represents a file system path.
Definition: gpath.h:82
std::string str() const
Returns the path string.
Definition: gpath.h:243
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:36
STL namespace.
Opaque leaky string pointer wrapper created by G::Cleanup::arg().
Definition: gcleanup.h:55