1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <signals.h>
26 #include <cleanup.h>
27 #include <known_dirs.h> /* GetStateDir() */
28 #include <file_lib.h> /* FILE_SEPARATOR */
29
30 static bool PENDING_TERMINATION = false; /* GLOBAL_X */
31
32 static bool RELOAD_CONFIG = false; /* GLOBAL_X */
33 /********************************************************************/
34
IsPendingTermination(void)35 bool IsPendingTermination(void)
36 {
37 return PENDING_TERMINATION;
38 }
39
ReloadConfigRequested(void)40 bool ReloadConfigRequested(void)
41 {
42 return RELOAD_CONFIG;
43 }
44
ClearRequestReloadConfig()45 void ClearRequestReloadConfig()
46 {
47 RELOAD_CONFIG = false;
48 }
49
50 /********************************************************************/
51
52 static int SIGNAL_PIPE[2] = { -1, -1 }; /* GLOBAL_C */
53
CloseSignalPipe(void)54 static void CloseSignalPipe(void)
55 {
56 int c = 2;
57 while (c > 0)
58 {
59 c--;
60 if (SIGNAL_PIPE[c] >= 0)
61 {
62 close(SIGNAL_PIPE[c]);
63 SIGNAL_PIPE[c] = -1;
64 }
65 }
66 }
67
68 /**
69 * Make a pipe that can be used to flag that a signal has arrived.
70 * Using a pipe avoids race conditions, since it saves its values until emptied.
71 * Use GetSignalPipe() to get the pipe.
72 * Note that we use a real socket as the pipe, because Windows only supports
73 * using select() with real sockets. This means also using send() and recv()
74 * instead of write() and read().
75 */
MakeSignalPipe(void)76 void MakeSignalPipe(void)
77 {
78 if (socketpair(AF_UNIX, SOCK_STREAM, 0, SIGNAL_PIPE) != 0)
79 {
80 Log(LOG_LEVEL_CRIT, "Could not create internal communication pipe. Cannot continue. (socketpair: '%s')",
81 GetErrorStr());
82 DoCleanupAndExit(EXIT_FAILURE);
83 }
84
85 RegisterCleanupFunction(&CloseSignalPipe);
86
87 for (int c = 0; c < 2; c++)
88 {
89 #ifdef __MINGW32__
90 u_long enable = 1;
91 int ret = ioctlsocket(SIGNAL_PIPE[c], FIONBIO, &enable);
92 #define CNTLNAME "ioctlsocket"
93 #else /* Unix: */
94 int ret = fcntl(SIGNAL_PIPE[c], F_SETFL, O_NONBLOCK);
95 #define CNTLNAME "fcntl"
96 #endif /* __MINGW32__ */
97
98 if (ret != 0)
99 {
100 Log(LOG_LEVEL_CRIT,
101 "Could not unblock internal communication pipe. "
102 "Cannot continue. (" CNTLNAME ": '%s')",
103 GetErrorStr());
104 DoCleanupAndExit(EXIT_FAILURE);
105 }
106 #undef CNTLNAME
107 }
108 }
109
110 /**
111 * Gets the signal pipe, which is non-blocking.
112 * Each byte read corresponds to one arrived signal.
113 * Note: Use recv() to read from the pipe, not read().
114 */
GetSignalPipe(void)115 int GetSignalPipe(void)
116 {
117 return SIGNAL_PIPE[0];
118 }
119
SignalNotify(int signum)120 static void SignalNotify(int signum)
121 {
122 unsigned char sig = (unsigned char)signum;
123 if (SIGNAL_PIPE[1] >= 0)
124 {
125 // send() is async-safe, according to POSIX.
126 if (send(SIGNAL_PIPE[1], &sig, 1, 0) < 0)
127 {
128 // These signal contention. Everything else is an error.
129 if (errno != EAGAIN
130 #ifndef __MINGW32__
131 && errno != EWOULDBLOCK
132 #endif
133 )
134 {
135 // This is not async safe, but if we get in here there's something really weird
136 // going on.
137 Log(LOG_LEVEL_CRIT, "Could not write to signal pipe. Unsafe to continue. (write: '%s')",
138 GetErrorStr());
139 _exit(EXIT_FAILURE);
140 }
141 }
142 }
143 }
144
HandleSignalsForAgent(int signum)145 void HandleSignalsForAgent(int signum)
146 {
147 switch (signum)
148 {
149 case SIGTERM:
150 case SIGINT:
151 /* TODO don't exit from the signal handler, just set a flag. Reason is
152 * that all the cleanup() hooks we register are not reentrant. */
153 DoCleanupAndExit(0);
154 case SIGBUS:
155 /* SIGBUS almost certainly means a violation of mmap() area boundaries
156 * or some mis-aligned memory access. IOW, an LMDB corruption. */
157 {
158 char filename[PATH_MAX] = { 0 }; /* trying to avoid memory allocation */
159 xsnprintf(filename, PATH_MAX, "%s%c%s",
160 GetStateDir(), FILE_SEPARATOR, CF_DB_REPAIR_TRIGGER);
161 int fd = open(filename, O_CREAT|O_RDWR, CF_PERMS_DEFAULT);
162 if (fd != -1)
163 {
164 close(fd);
165 }
166
167 /* avoid calling complex logging functions */
168 fprintf(stdout, "process killed by SIGBUS\n");
169
170 /* else: we tried, nothing more to do in the limited environment of a
171 * signal handler */
172 _exit(1);
173 }
174 break;
175 case SIGUSR1:
176 LogSetGlobalLevel(LOG_LEVEL_DEBUG);
177 break;
178 case SIGUSR2:
179 LogSetGlobalLevel(LOG_LEVEL_NOTICE);
180 break;
181 default:
182 /* No action */
183 break;
184 }
185
186 SignalNotify(signum);
187
188 /* Reset the signal handler */
189 signal(signum, HandleSignalsForAgent);
190 }
191
192 /********************************************************************/
193
HandleSignalsForDaemon(int signum)194 void HandleSignalsForDaemon(int signum)
195 {
196 switch (signum)
197 {
198 case SIGTERM:
199 case SIGINT:
200 case SIGSEGV:
201 case SIGKILL:
202 PENDING_TERMINATION = true;
203 break;
204 case SIGBUS:
205 /* SIGBUS almost certainly means a violation of mmap() area boundaries
206 * or some mis-aligned memory access. IOW, an LMDB corruption. */
207 {
208 char filename[PATH_MAX] = { 0 }; /* trying to avoid memory allocation */
209 xsnprintf(filename, PATH_MAX, "%s%c%s",
210 GetStateDir(), FILE_SEPARATOR, CF_DB_REPAIR_TRIGGER);
211 int fd = open(filename, O_CREAT|O_RDWR, CF_PERMS_DEFAULT);
212 if (fd != -1)
213 {
214 close(fd);
215 }
216
217 /* avoid calling complex logging functions */
218 fprintf(stdout, "process killed by SIGBUS\n");
219
220 /* else: we tried, nothing more to do in the limited environment of a
221 * signal handler */
222 _exit(1);
223 }
224 break;
225 case SIGUSR1:
226 LogSetGlobalLevel(LOG_LEVEL_DEBUG);
227 break;
228 case SIGUSR2:
229 LogSetGlobalLevel(LOG_LEVEL_NOTICE);
230 break;
231 case SIGHUP:
232 RELOAD_CONFIG = true;
233 break;
234 case SIGPIPE:
235 default:
236 /* No action */
237 break;
238 }
239
240 /* Notify processes that use the signal pipe (cf-serverd). */
241 SignalNotify(signum);
242
243 /* Reset the signal handler. */
244 signal(signum, HandleSignalsForDaemon);
245 }
246