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
33 extern "C"
34{
35 void gcleanup_handler( int signum ) ;
36 using Handler = void (*)(int) ;
37}
38
39 namespace 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
81 G::CleanupImp::Link * G::CleanupImp::m_head = nullptr ;
82 G::CleanupImp::Link * G::CleanupImp::m_tail = nullptr ;
83 bool G::CleanupImp::m_atexit_active = false ;
84 bool G::CleanupImp::m_atexit_installed = false ;
85 std::array<int,4U> G::CleanupImp::m_signals = {{ SIGTERM , SIGINT , SIGHUP , SIGQUIT }} ;
86
87// ===
88
89 void G::Cleanup::init()
90{
91 CleanupImp::installIgnore( SIGPIPE ) ;
92}
93
94 void G::Cleanup::add( Fn fn , Arg arg )
95{
96 CleanupImp::add( fn , arg ) ;
97}
98
99#ifndef G_LIB_SMALL
100 void G::Cleanup::atexit( bool active )
101{
102 CleanupImp::atexit( active ) ;
103}
104#endif
105
106 void G::Cleanup::block() noexcept
107{
108 CleanupImp::block() ;
109}
110
111 void G::Cleanup::release() noexcept
112{
113 CleanupImp::release() ;
114}
115
116#ifndef G_LIB_SMALL
117 G::Cleanup::Arg G::Cleanup::arg( const char * p )
118{
119 return CleanupImp::duplicate( p?p:"" , p?std::strlen(p):0U ) ;
120}
121#endif
122
123#ifndef G_LIB_SMALL
124 G::Cleanup::Arg G::Cleanup::arg( const std::string & s )
125{
126 return CleanupImp::duplicate( s.data() , s.size() ) ;
127}
128#endif
129
130 G::Cleanup::Arg G::Cleanup::arg( const Path & p )
131{
132 std::string s = p.str() ;
133 return CleanupImp::duplicate( s.data() , s.size() , true ) ;
134}
135
136#ifndef G_LIB_SMALL
137 G::Cleanup::Arg G::Cleanup::arg( std::nullptr_t )
138{
139 return CleanupImp::duplicate( nullptr , 0U ) ;
140}
141#endif
142
143// ==
144
145#ifndef G_LIB_SMALL
146 bool G::Cleanup::Arg::isPath() const noexcept
147{
148 return m_is_path ;
149}
150#endif
151
152 const char * G::Cleanup::Arg::str() const noexcept
153{
154 return m_ptr ;
155}
156
157// ===
158
159 void 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
169 void 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
191 char * G::CleanupImp::new_arg_ignore_leak( std::size_t n )
192{
193 return static_cast<char*>( operator new(n) ) ; // NOLINT // ignore leak
194}
195
196 G::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
212 void 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
220 bool 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
229 void G::CleanupImp::installDefault( int signum )
230{
231 install( signum , SIG_DFL ) ;
232}
233#endif
234
235#ifndef G_LIB_SMALL
236 void G::CleanupImp::installDefault( const G::SignalSafe & , int signum ) noexcept
237{
238 install( signum , SIG_DFL , std::nothrow ) ;
239}
240#endif
241
242 void G::CleanupImp::installIgnore( int signum )
243{
244 install( signum , SIG_IGN ) ; // NOLINT macro shenanigans
245}
246
247 void 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
256 void 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
263 void 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
273 void G::CleanupImp::atexitHandler() noexcept
274{
275 if( m_atexit_active )
276 callHandlers() ;
277}
278
279 void G::CleanupImp::callHandlers() noexcept
280{
281 if( !callHandlersOnce( SignalSafe() ) )
282 {
283 Root::atExit( SignalSafe() ) ;
284 callHandlersOnce( SignalSafe() ) ;
285 }
286}
287
288 bool 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
307 extern "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
315 void 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
326 void 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
G::Cleanup::arg
static Arg arg(const char *)
Duplicates a c-string for add().
Definition: gcleanup_unix.cpp:117
G::Cleanup::atexit
static void atexit(bool active=true)
Ensures that the cleanup functions are also called via atexit(), in addition to abnormal-termination ...
Definition: gcleanup_unix.cpp:100
G::Cleanup::release
static void release() noexcept
Releases block()ed signals.
Definition: gcleanup_unix.cpp:111
G::Cleanup::block
static void block() noexcept
Temporarily blocks signals until release()d.
Definition: gcleanup_unix.cpp:106
G::Cleanup::init
static void init()
An optional early-initialisation function. May be called more than once.
Definition: gcleanup_unix.cpp:89
G::Cleanup::add
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...
Definition: gcleanup_unix.cpp:94
G::Path
A Path object represents a file system path.
Definition: gpath.h:82
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.h:243
G::Root::atExit
static void atExit() noexcept
Re-acquires special privileges just before process exit.
Definition: groot.cpp:68
G::SignalSafe
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:37
gassert.h
gcleanup.h
glog.h
gprocess.h
groot.h
G
Low-level classes.
Definition: garg.h:36
std
STL namespace.
G::Cleanup::Arg
Opaque leaky string pointer wrapper created by G::Cleanup::arg().
Definition: gcleanup.h:55