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