1 // Copyright (C) 2010-2014 David Sugar, Tycho Softworks.
2 // Copyright (C) 2015 Cherokees of Idaho.
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
18
19 #ifdef HAVE_SYS_INOTIFY_H
20 #include <sys/inotify.h>
21 #include <sys/poll.h>
22 #endif
23
24 namespace sipwitch {
25
26 #ifdef HAVE_SIGWAIT
27
28 psignals psignals::thread;
29
30 psignals::psignals() :
32 {
33 shutdown = started = false;
34 }
35
36 psignals::~psignals()
37 {
38 if(!shutdown)
39 cancel();
40 }
41
42 void psignals::cancel(void)
43 {
44 if(started) {
45 shutdown = true;
46 #ifdef __FreeBSD__
47 raise(SIGINT);
48 #endif
49 pthread_kill(tid, SIGALRM);
50 join();
51 }
52 }
53
54 void psignals::run(void)
55 {
56 int signo;
57 unsigned period = 900;
58
59 started = true;
60 shell::log(
DEBUG1,
"starting signal handler");
61
62 for(;;) {
63 alarm(period);
64 #ifdef HAVE_SIGWAIT2
65 int result = sigwait(&sigs, &signo);
66 if(result) {
67 alarm(0);
68 shell::log(shell::ERR, "signal handler error %d", errno);
69 Thread::sleep(1000);
70 continue;
71 }
72 #else
73 signo = sigwait(&sigs);
74 if(signo < 0) {
75 alarm(0);
76 shell::log(shell::ERR, "signal handler error %d", errno);
77 Thread:sleep(1000);
78 continue;
79 }
80 #endif
81 alarm(0);
82 if(shutdown)
83 break;
84
85 shell::log(
DEBUG1,
"received signal %d", signo);
86
87 switch(signo) {
88 case SIGALRM:
89 shell::log(shell::INFO, "system housekeeping");
90 // registry::cleanup(period);
91 events::sync(period);
92 cache::cleanup();
93 break;
94 case SIGINT:
95 case SIGTERM:
96 control::send("down");
97 break;
98 case SIGUSR1:
99 control::send("snapshot");
100 break;
101 case SIGHUP:
102 control::send("reload");
103 break;
104 default:
105 break;
106 }
107 }
108 shell::log(
DEBUG1,
"stopping signal handler");
109 }
110
111 void psignals::service(
const char *
name)
112 {
113 }
114
115 void psignals::setup(void)
116 {
117 sigemptyset(&thread.sigs);
118 sigaddset(&thread.sigs, SIGALRM);
119 sigaddset(&thread.sigs, SIGHUP);
120 sigaddset(&thread.sigs, SIGINT);
121 sigaddset(&thread.sigs, SIGTERM);
122 sigaddset(&thread.sigs, SIGUSR1);
123 pthread_sigmask(SIG_BLOCK, &thread.sigs, NULL);
124
125 signal(SIGPIPE, SIG_IGN);
126 }
127
128 void psignals::start(void)
129 {
130 thread.background();
131 }
132
133 void psignals::stop(void)
134 {
135 thread.cancel();
136 }
137
138 #elif defined(WIN32)
139
140 static SERVICE_STATUS_HANDLE hStatus = 0;
141 static SERVICE_STATUS status;
142
143 static void WINAPI handler(DWORD sigint)
144 {
145 switch(sigint) {
146 case 128:
147 // control::request("reload");
148 return;
149 case 129:
150 // control::request("snapshot");
151 case SERVICE_CONTROL_SHUTDOWN:
152 case SERVICE_CONTROL_STOP:
153 status.dwCurrentState = SERVICE_STOP_PENDING;
154 status.dwWin32ExitCode = 0;
155 status.dwCheckPoint = 0;
156 status.dwWaitHint = 6000;
157 SetServiceStatus(hStatus, &status);
158 // control::request("down");
159 break;
160 default:
161 break;
162 }
163 }
164
165 void psignals::service(
const char *
name)
166 {
167 memset(&status, 0, sizeof(SERVICE_STATUS));
168 status.dwServiceType = SERVICE_WIN32;
169 status.dwCurrentState = SERVICE_START_PENDING;
170 status.dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN;
171 hStatus = ::RegisterServiceCtrlHandler(name, &handler);
172 }
173
174 void psignals::setup(void)
175 {
176 }
177
178 void psignals::start(void)
179 {
180 if(!hStatus)
181 return;
182
183 status.dwCurrentState = SERVICE_RUNNING;
184 ::SetServiceStatus(hStatus, &status);
185 }
186
187 void psignals::stop(void)
188 {
189 if(!hStatus)
190 return;
191
192 status.dwCurrentState = SERVICE_STOPPED;
193 ::SetServiceStatus(hStatus, &status);
194 }
195
196 #else
197
198 void psignals::service(
const char *name)
199 {
200 }
201
202 void psignals::setup(
void)
203 {
204 }
205
206 void psignals::start(
void)
207 {
208 }
209
210 void psignals::stop(
void)
211 {
212 }
213
214 #endif
215
216 #ifdef HAVE_SYS_INOTIFY_H
217
218 static const char *dirpath = NULL;
219 static const char *cachepath = NULL;
220 static fd_t watcher = -1;
221 static uint32_t dirnode;
222 static uint32_t cachenode;
223
225
227 {
228 }
229
230 notify::~notify()
231 {
233 }
234
236 {
239
240 if(!dirpath)
242
243 if(fsys::is_dir(dirpath))
244 thread.background();
245 else
246 shell::log(shell::ERR, "notify failed; %s missing", dirpath);
247 }
248
249 void notify::run(void)
250 {
251 static bool logged = false;
252 timeout_t timeout = -1;
253 unsigned updates = 0;
254 struct pollfd pfd;
255
256 shell::log(
DEBUG1,
"notify watching %s", dirpath);
257
258 watcher = inotify_init();
259 dirnode = inotify_add_watch(watcher, dirpath, IN_CLOSE_WRITE|IN_MOVED_TO|IN_MOVED_FROM|IN_DELETE|IN_DONT_FOLLOW);
260 if(cachepath) {
261 cachenode = inotify_add_watch(watcher, cachepath, IN_CLOSE_WRITE|IN_MOVED_TO|IN_MOVED_FROM|IN_DELETE|IN_DONT_FOLLOW);
262 shell::log(
DEBUG1,
"notify watching cache %s", cachepath);
263 }
264
265 while(watcher != -1) {
266 // we want 500 ms of inactivity before actual updating...
267 if(updates)
268 timeout = 500;
269 else
270 timeout = 1000;
271
272 pfd.fd = watcher;
273 pfd.events = POLLIN | POLLNVAL | POLLERR;
274 pfd.revents = 0;
275 int result = poll(&pfd, 1, timeout);
276 // if error, we sleep....we should also not get errors...
277 if(result < 0) {
278 if(!logged) {
279 shell::log(shell::ERR, "notify error %d", errno);
280 logged = true;
281 }
282 // throttle on repeated errors...
283 Thread::sleep(1000);
284 continue;
285 }
286 if(!result) {
287 if(!updates)
288 continue;
289
290 // clear updates and process config on timeout
292 updates = 0;
293 continue;
294 }
295 if(pfd.revents & (POLLNVAL|POLLERR))
296 break;
297
298 if(pfd.revents & POLLIN) {
299 char buffer[512];
300 size_t offset = 0;
301
302 size_t len = ::read(watcher, &buffer, sizeof(buffer));
303 if(len < sizeof(struct inotify_event)) {
304 shell::log(shell::ERR, "notify failed to read inotify");
305 continue;
306 }
307
308 while(offset < len) {
309 struct inotify_event *event = (struct inotify_event *)&buffer[offset];
310 if(!event->len)
311 break;
312
313 // only if xml files updated do we care...
314 const char *ext = strrchr(event->name, '.');
315 if(ext && eq_case(ext, ".xml")) {
316 shell::log(
DEBUG2,
"%s updated", event->name);
317 ++updates;
318 }
319 offset += sizeof(struct inotify_event) + event->len;
320 }
321 }
322 }
323
324 shell::log(
DEBUG1,
"notify terminating");
325 }
326
328 {
329 if(watcher != -1) {
330 fd_t fd = watcher;
331 watcher = -1;
332 ::close(fd);
333 thread.join();
334 }
335 }
336
337 #else
338
340 {
341 }
342
344 {
345 }
346
347 #endif
348
349 } // end namespace
350
static bool send(const char *format,...) __PRINTF(1
Send a printf-style message to the control fifo via the file system.
static const char * env(const char *id)
Return the value of a server environment variable.