Rev 100 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
45 | luk | 1 | |
2 | /// inotify cron daemon main file |
||
3 | /** |
||
4 | * \file icd-main.cpp |
||
5 | * |
||
6 | * inotify cron system |
||
7 | * |
||
108 | luk | 8 | * Copyright (C) 2006, 2007, 2008, 2012 Lukas Jelinek, <lukas@aiken.cz> |
45 | luk | 9 | * |
10 | * This program is free software; you can use it, redistribute |
||
11 | * it and/or modify it under the terms of the GNU General Public |
||
12 | * License, version 2 (see LICENSE-GPL). |
||
13 | * |
||
108 | luk | 14 | * Credits: |
15 | * Christian Ruppert (new include to build with GCC 4.4+) |
||
16 | * |
||
45 | luk | 17 | */ |
18 | |||
19 | #include <map> |
||
20 | #include <signal.h> |
||
21 | #include <wait.h> |
||
61 | luk | 22 | #include <fcntl.h> |
45 | luk | 23 | #include <pwd.h> |
24 | #include <dirent.h> |
||
25 | #include <syslog.h> |
||
26 | #include <errno.h> |
||
47 | luk | 27 | #include <sys/poll.h> |
59 | luk | 28 | #include <sys/stat.h> |
108 | luk | 29 | #include <cstdio> |
100 | luk | 30 | #include <cstring> |
45 | luk | 31 | |
32 | #include "inotify-cxx.h" |
||
69 | luk | 33 | #include "appinst.h" |
34 | #include "appargs.h" |
||
55 | luk | 35 | |
36 | #include "incron.h" |
||
45 | luk | 37 | #include "incrontab.h" |
38 | #include "usertable.h" |
||
69 | luk | 39 | #include "incroncfg.h" |
45 | luk | 40 | |
41 | |||
49 | luk | 42 | /// Logging options (console as fallback, log PID) |
45 | luk | 43 | #define INCRON_LOG_OPTS (LOG_CONS | LOG_PID) |
49 | luk | 44 | |
45 | /// Logging facility (use CRON) |
||
45 | luk | 46 | #define INCRON_LOG_FACIL LOG_CRON |
47 | |||
69 | luk | 48 | /// incrond version string |
49 | #define INCROND_VERSION INCROND_NAME " " INCRON_VERSION |
||
45 | luk | 50 | |
69 | luk | 51 | /// incrontab description string |
52 | #define INCROND_DESCRIPTION "incrond - inotify cron daemon\n" \ |
||
100 | luk | 53 | "(c) Lukas Jelinek, 2006, 2007, 2008" |
45 | luk | 54 | |
69 | luk | 55 | /// incrontab help string |
56 | #define INCROND_HELP INCROND_DESCRIPTION "\n\n" \ |
||
57 | "usage: incrond [<options>]\n\n" \ |
||
58 | "<operation> may be one of the following:\n" \ |
||
59 | "These options may be used:\n" \ |
||
60 | " -?, --about gives short information about program\n" \ |
||
61 | " -h, --help prints this help text\n" \ |
||
62 | " -n, --foreground runs on foreground (no daemonizing)\n" \ |
||
63 | " -k, --kill terminates running instance of incrond\n" \ |
||
64 | " -f <FILE>, --config=<FILE> overrides default configuration file (requires root privileges)\n" \ |
||
65 | " -V, --version prints program version\n\n" \ |
||
100 | luk | 66 | "For reporting bugs please use http://bts.aiken.cz\n" |
67 | luk | 67 | |
69 | luk | 68 | |
69 | |||
49 | luk | 70 | /// User name to user table mapping table |
45 | luk | 71 | SUT_MAP g_ut; |
72 | |||
49 | luk | 73 | /// Finish program yes/no |
47 | luk | 74 | volatile bool g_fFinish = false; |
45 | luk | 75 | |
61 | luk | 76 | /// Pipe for notifying about dead children |
77 | int g_cldPipe[2]; |
||
49 | luk | 78 | |
61 | luk | 79 | // Buffer for emptying child pipe |
80 | #define CHILD_PIPE_BUF_LEN 32 |
||
81 | char g_cldPipeBuf[CHILD_PIPE_BUF_LEN]; |
||
82 | |||
67 | luk | 83 | /// Daemonize true/false |
84 | bool g_daemon = true; |
||
61 | luk | 85 | |
108 | luk | 86 | /// Second to wait on EAGAIN |
87 | #define POLL_EAGAIN_WAIT 3 |
||
88 | |||
47 | luk | 89 | /// Handles a signal. |
90 | /** |
||
91 | * For SIGTERM and SIGINT it sets the program finish variable. |
||
61 | luk | 92 | * For SIGCHLD it writes a character into the notification pipe |
93 | * (this is a workaround made due to disability to reliably |
||
94 | * wait for dead children). |
||
47 | luk | 95 | * |
96 | * \param[in] signo signal number |
||
97 | */ |
||
45 | luk | 98 | void on_signal(int signo) |
99 | { |
||
61 | luk | 100 | switch (signo) { |
101 | case SIGTERM: |
||
102 | case SIGINT: |
||
103 | g_fFinish = true; |
||
104 | break; |
||
105 | case SIGCHLD: |
||
106 | // first empty pipe (to prevent internal buffer overflow) |
||
107 | do {} while (read(g_cldPipe[0], g_cldPipeBuf, CHILD_PIPE_BUF_LEN) > 0); |
||
108 | |||
109 | // now write one character |
||
100 | luk | 110 | if (write(g_cldPipe[1], "X", 1) <= 0) { |
111 | syslog(LOG_WARNING, "cannot send SIGCHLD token to notification pipe"); |
||
112 | } |
||
61 | luk | 113 | break; |
114 | default:; |
||
115 | } |
||
45 | luk | 116 | } |
117 | |||
118 | |||
69 | luk | 119 | |
120 | |||
75 | luk | 121 | /// Attempts to load all (user and system) incron tables. |
47 | luk | 122 | /** |
123 | * Loaded tables are registered for processing events. |
||
124 | * |
||
125 | * \param[in] pEd inotify event dispatcher |
||
126 | * |
||
127 | * \throw InotifyException thrown if base table directory cannot be read |
||
128 | */ |
||
67 | luk | 129 | void load_tables(EventDispatcher* pEd) throw (InotifyException) |
45 | luk | 130 | { |
67 | luk | 131 | // WARNING - this function has not been optimized!!! |
132 | |||
69 | luk | 133 | std::string s; |
134 | if (!IncronCfg::GetValue("system_table_dir", s)) |
||
135 | throw InotifyException("configuration system is corrupted", EINVAL); |
||
136 | |||
137 | DIR* d = opendir(s.c_str()); |
||
67 | luk | 138 | if (d != NULL) { |
139 | syslog(LOG_NOTICE, "loading system tables"); |
||
140 | |||
141 | struct dirent* pDe = NULL; |
||
142 | while ((pDe = readdir(d)) != NULL) { |
||
143 | std::string un(pDe->d_name); |
||
69 | luk | 144 | std::string path(IncronCfg::BuildPath(s, pDe->d_name)); |
67 | luk | 145 | |
146 | bool ok = pDe->d_type == DT_REG; |
||
147 | if (pDe->d_type == DT_UNKNOWN) { |
||
148 | struct stat st; |
||
69 | luk | 149 | if (stat(path.c_str(), &st) == 0) |
67 | luk | 150 | ok = S_ISREG(st.st_mode); |
151 | } |
||
152 | |||
153 | if (ok) { |
||
154 | syslog(LOG_INFO, "loading table %s", pDe->d_name); |
||
155 | UserTable* pUt = new UserTable(pEd, un, true); |
||
69 | luk | 156 | g_ut.insert(SUT_MAP::value_type(path, pUt)); |
67 | luk | 157 | pUt->Load(); |
158 | } |
||
159 | } |
||
160 | |||
161 | closedir(d); |
||
162 | } |
||
163 | else { |
||
164 | syslog(LOG_WARNING, "cannot open system table directory (ignoring)"); |
||
165 | } |
||
166 | |||
69 | luk | 167 | if (!IncronCfg::GetValue("user_table_dir", s)) |
168 | throw InotifyException("configuration system is corrupted", EINVAL); |
||
169 | |||
170 | d = opendir(s.c_str()); |
||
47 | luk | 171 | if (d == NULL) |
67 | luk | 172 | throw InotifyException("cannot open user table directory", errno); |
45 | luk | 173 | |
174 | syslog(LOG_NOTICE, "loading user tables"); |
||
175 | |||
176 | struct dirent* pDe = NULL; |
||
177 | while ((pDe = readdir(d)) != NULL) { |
||
178 | std::string un(pDe->d_name); |
||
69 | luk | 179 | std::string path(IncronCfg::BuildPath(s, pDe->d_name)); |
59 | luk | 180 | |
181 | bool ok = pDe->d_type == DT_REG; |
||
182 | if (pDe->d_type == DT_UNKNOWN) { |
||
183 | struct stat st; |
||
69 | luk | 184 | if (stat(path.c_str(), &st) == 0) |
59 | luk | 185 | ok = S_ISREG(st.st_mode); |
186 | } |
||
187 | |||
188 | if (ok) { |
||
67 | luk | 189 | if (UserTable::CheckUser(pDe->d_name)) { |
45 | luk | 190 | syslog(LOG_INFO, "loading table for user %s", pDe->d_name); |
67 | luk | 191 | UserTable* pUt = new UserTable(pEd, un, false); |
69 | luk | 192 | g_ut.insert(SUT_MAP::value_type(path, pUt)); |
45 | luk | 193 | pUt->Load(); |
194 | } |
||
195 | else { |
||
196 | syslog(LOG_WARNING, "table for invalid user %s found (ignored)", pDe->d_name); |
||
197 | } |
||
198 | } |
||
199 | } |
||
200 | |||
201 | closedir(d); |
||
202 | } |
||
203 | |||
75 | luk | 204 | /// Deallocates all memory used by incron tables and unregisters them from the dispatcher. |
205 | /** |
||
206 | * \param[in] pEd event dispatcher |
||
207 | */ |
||
208 | void free_tables(EventDispatcher* pEd) |
||
209 | { |
||
210 | pEd->Clear(); |
||
211 | |||
212 | SUT_MAP::iterator it = g_ut.begin(); |
||
213 | while (it != g_ut.end()) { |
||
214 | UserTable* pUt = (*it).second; |
||
215 | delete pUt; |
||
216 | it++; |
||
217 | } |
||
218 | |||
219 | g_ut.clear(); |
||
220 | } |
||
221 | |||
61 | luk | 222 | /// Prepares a 'dead/done child' notification pipe. |
223 | /** |
||
224 | * This function returns no value at all and on error it |
||
225 | * throws an exception. |
||
226 | */ |
||
227 | void prepare_pipe() |
||
228 | { |
||
229 | g_cldPipe[0] = -1; |
||
230 | g_cldPipe[1] = -1; |
||
231 | |||
232 | if (pipe(g_cldPipe) != 0) |
||
233 | throw InotifyException("cannot create notification pipe", errno, NULL); |
||
234 | |||
235 | for (int i=0; i<2; i++) { |
||
236 | int res = fcntl(g_cldPipe[i], F_GETFL); |
||
237 | if (res == -1) |
||
238 | throw InotifyException("cannot get pipe flags", errno, NULL); |
||
239 | |||
240 | res |= O_NONBLOCK; |
||
241 | |||
242 | if (fcntl(g_cldPipe[i], F_SETFL, res) == -1) |
||
243 | throw InotifyException("cannot set pipe flags", errno, NULL); |
||
67 | luk | 244 | |
245 | res = fcntl(g_cldPipe[i], F_GETFD); |
||
246 | if (res == -1) |
||
247 | throw InotifyException("cannot get pipe flags", errno, NULL); |
||
248 | |||
249 | res |= FD_CLOEXEC; |
||
250 | |||
251 | if (fcntl(g_cldPipe[i], F_SETFD, res) == -1) |
||
252 | throw InotifyException("cannot set pipe flags", errno, NULL); |
||
61 | luk | 253 | } |
254 | } |
||
255 | |||
67 | luk | 256 | /// Checks whether a parameter string is a specific command. |
257 | /** |
||
258 | * The string is accepted if it equals either the short or long |
||
259 | * form of the command. |
||
260 | * |
||
261 | * \param[in] s checked string |
||
262 | * \param[in] shortCmd short form of command |
||
263 | * \param[in] longCmd long form of command |
||
264 | * \return true = string accepted, false = otherwise |
||
265 | */ |
||
75 | luk | 266 | /* |
67 | luk | 267 | bool check_parameter(const char* s, const char* shortCmd, const char* longCmd) |
268 | { |
||
269 | return strcmp(s, shortCmd) == 0 |
||
270 | || strcmp(s, longCmd) == 0; |
||
271 | } |
||
75 | luk | 272 | */ |
67 | luk | 273 | |
274 | /// Initializes a poll array. |
||
275 | /** |
||
69 | luk | 276 | * \param[out] pfd poll structure array |
67 | luk | 277 | * \param[in] pipefd pipe file descriptor |
278 | * \param[in] infd inotify infrastructure file descriptor |
||
279 | */ |
||
280 | void init_poll_array(struct pollfd pfd[], int pipefd, int infd) |
||
281 | { |
||
282 | pfd[0].fd = pipefd; |
||
283 | pfd[0].events = (short) POLLIN; |
||
284 | pfd[0].revents = (short) 0; |
||
285 | pfd[1].fd = infd; |
||
286 | pfd[1].events = (short) POLLIN; |
||
287 | pfd[1].revents = (short) 0; |
||
288 | } |
||
289 | |||
290 | |||
47 | luk | 291 | /// Main application function. |
292 | /** |
||
293 | * \param[in] argc argument count |
||
294 | * \param[in] argv argument array |
||
295 | * \return 0 on success, 1 on error |
||
296 | * |
||
297 | * \attention In daemon mode, it finishes immediately. |
||
298 | */ |
||
45 | luk | 299 | int main(int argc, char** argv) |
300 | { |
||
69 | luk | 301 | AppArgs::Init(); |
302 | |||
303 | if (!( AppArgs::AddOption("about", '?', AAT_NO_VALUE, false) |
||
304 | && AppArgs::AddOption("help", 'h', AAT_NO_VALUE, false) |
||
305 | && AppArgs::AddOption("foreground", 'n', AAT_NO_VALUE, false) |
||
306 | && AppArgs::AddOption("kill", 'k', AAT_NO_VALUE, false) |
||
79 | luk | 307 | && AppArgs::AddOption("config", 'f', AAT_MANDATORY_VALUE, false) |
308 | && AppArgs::AddOption("version", 'V', AAT_NO_VALUE, false))) |
||
69 | luk | 309 | { |
310 | fprintf(stderr, "error while initializing application"); |
||
67 | luk | 311 | return 1; |
312 | } |
||
313 | |||
69 | luk | 314 | AppArgs::Parse(argc, argv); |
315 | |||
316 | if (AppArgs::ExistsOption("help")) { |
||
317 | fprintf(stderr, "%s\n", INCROND_HELP); |
||
318 | return 0; |
||
319 | } |
||
320 | |||
321 | if (AppArgs::ExistsOption("about")) { |
||
322 | fprintf(stderr, "%s\n", INCROND_DESCRIPTION); |
||
323 | return 0; |
||
324 | } |
||
325 | |||
326 | if (AppArgs::ExistsOption("version")) { |
||
327 | fprintf(stderr, "%s\n", INCROND_VERSION); |
||
328 | return 0; |
||
329 | } |
||
330 | |||
331 | IncronCfg::Init(); |
||
332 | |||
71 | luk | 333 | std::string cfg; |
334 | if (!AppArgs::GetOption("config", cfg)) |
||
335 | cfg = INCRON_CONFIG; |
||
336 | IncronCfg::Load(cfg); |
||
337 | |||
338 | std::string lckdir; |
||
339 | IncronCfg::GetValue("lockfile_dir", lckdir); |
||
340 | std::string lckfile; |
||
341 | IncronCfg::GetValue("lockfile_name", lckfile); |
||
342 | AppInstance app(lckfile, lckdir); |
||
343 | |||
69 | luk | 344 | if (AppArgs::ExistsOption("kill")) { |
345 | fprintf(stderr, "attempting to terminate a running instance of incrond...\n"); |
||
73 | luk | 346 | if (app.Terminate()) { |
75 | luk | 347 | fprintf(stderr, "the instance notified, going down\n"); |
67 | luk | 348 | return 0; |
349 | } |
||
69 | luk | 350 | else { |
351 | fprintf(stderr, "error - incrond probably not running\n"); |
||
67 | luk | 352 | return 1; |
353 | } |
||
354 | } |
||
355 | |||
69 | luk | 356 | if (AppArgs::ExistsOption("foreground")) |
357 | g_daemon = false; |
||
45 | luk | 358 | |
69 | luk | 359 | |
360 | openlog(INCROND_NAME, INCRON_LOG_OPTS, INCRON_LOG_FACIL); |
||
361 | |||
55 | luk | 362 | syslog(LOG_NOTICE, "starting service (version %s, built on %s %s)", INCRON_VERSION, __DATE__, __TIME__); |
45 | luk | 363 | |
69 | luk | 364 | AppArgs::Destroy(); |
365 | |||
366 | int ret = 0; |
||
367 | |||
368 | std::string sysBase; |
||
369 | std::string userBase; |
||
370 | |||
371 | if (!IncronCfg::GetValue("system_table_dir", sysBase)) |
||
372 | throw InotifyException("configuration is corrupted", EINVAL); |
||
373 | |||
374 | if (access(sysBase.c_str(), R_OK) != 0) { |
||
375 | syslog(LOG_CRIT, "cannot read directory for system tables (%s): (%i) %s", sysBase.c_str(), errno, strerror(errno)); |
||
376 | if (!g_daemon) |
||
377 | fprintf(stderr, "cannot read directory for system tables (%s): (%i) %s", sysBase.c_str(), errno, strerror(errno)); |
||
378 | ret = 1; |
||
379 | goto error; |
||
380 | } |
||
381 | |||
382 | if (!IncronCfg::GetValue("user_table_dir", userBase)) |
||
383 | throw InotifyException("configuration is corrupted", EINVAL); |
||
384 | |||
385 | if (access(userBase.c_str(), R_OK) != 0) { |
||
386 | syslog(LOG_CRIT, "cannot read directory for user tables (%s): (%i) %s", userBase.c_str(), errno, strerror(errno)); |
||
387 | if (!g_daemon) |
||
388 | fprintf(stderr, "cannot read directory for user tables (%s): (%i) %s", userBase.c_str(), errno, strerror(errno)); |
||
389 | ret = 1; |
||
390 | goto error; |
||
391 | } |
||
392 | |||
47 | luk | 393 | try { |
67 | luk | 394 | if (g_daemon) |
100 | luk | 395 | if (daemon(0, 0) == -1) { |
396 | syslog(LOG_CRIT, "daemonizing failed: (%i) %s", errno, strerror(errno)); |
||
397 | fprintf(stderr, "daemonizing failed: (%i) %s\n", errno, strerror(errno)); |
||
398 | ret = 1; |
||
399 | goto error; |
||
400 | } |
||
69 | luk | 401 | |
402 | try { |
||
403 | if (!app.Lock()) { |
||
404 | syslog(LOG_CRIT, "another instance of incrond already running"); |
||
405 | if (!g_daemon) |
||
406 | fprintf(stderr, "another instance of incrond already running\n"); |
||
407 | ret = 1; |
||
408 | goto error; |
||
409 | } |
||
410 | } catch (AppInstException e) { |
||
411 | syslog(LOG_CRIT, "instance lookup failed: (%i) %s", e.GetErrorNumber(), strerror(e.GetErrorNumber())); |
||
412 | if (!g_daemon) |
||
413 | fprintf(stderr, "instance lookup failed: (%i) %s\n", e.GetErrorNumber(), strerror(e.GetErrorNumber())); |
||
414 | ret = 1; |
||
415 | goto error; |
||
416 | } |
||
67 | luk | 417 | |
418 | prepare_pipe(); |
||
419 | |||
47 | luk | 420 | Inotify in; |
421 | in.SetNonBlock(true); |
||
67 | luk | 422 | in.SetCloseOnExec(true); |
47 | luk | 423 | |
75 | luk | 424 | uint32_t wm = IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_UNMOUNT; |
69 | luk | 425 | InotifyWatch stw(sysBase, wm); |
67 | luk | 426 | in.Add(stw); |
69 | luk | 427 | InotifyWatch utw(userBase, wm); |
67 | luk | 428 | in.Add(utw); |
47 | luk | 429 | |
67 | luk | 430 | EventDispatcher ed(g_cldPipe[0], &in, &stw, &utw); |
431 | |||
47 | luk | 432 | try { |
67 | luk | 433 | load_tables(&ed); |
47 | luk | 434 | } catch (InotifyException e) { |
435 | int err = e.GetErrorNumber(); |
||
436 | syslog(LOG_CRIT, "%s: (%i) %s", e.GetMessage().c_str(), err, strerror(err)); |
||
69 | luk | 437 | ret = 1; |
438 | goto error; |
||
47 | luk | 439 | } |
440 | |||
75 | luk | 441 | ed.Rebuild(); // not too efficient, but simple |
442 | |||
47 | luk | 443 | signal(SIGTERM, on_signal); |
444 | signal(SIGINT, on_signal); |
||
445 | signal(SIGCHLD, on_signal); |
||
446 | |||
447 | syslog(LOG_NOTICE, "ready to process filesystem events"); |
||
448 | |||
449 | while (!g_fFinish) { |
||
450 | |||
67 | luk | 451 | int res = poll(ed.GetPollData(), ed.GetSize(), -1); |
61 | luk | 452 | |
47 | luk | 453 | if (res > 0) { |
67 | luk | 454 | if (ed.ProcessEvents()) |
61 | luk | 455 | UserTable::FinishDone(); |
47 | luk | 456 | } |
457 | else if (res < 0) { |
||
108 | luk | 458 | switch (errno) { |
459 | case EINTR: // syscall interrupted - continue polling |
||
460 | break; |
||
461 | case EAGAIN: // not enough resources - wait a moment and try again |
||
462 | syslog(LOG_WARNING, "polling failed due to resource shortage, retrying later..."); |
||
463 | sleep(POLL_EAGAIN_WAIT); |
||
464 | break; |
||
465 | default: |
||
466 | throw InotifyException("polling failed", errno, NULL); |
||
467 | } |
||
47 | luk | 468 | } |
469 | |||
45 | luk | 470 | } |
61 | luk | 471 | |
75 | luk | 472 | free_tables(&ed); |
473 | |||
61 | luk | 474 | if (g_cldPipe[0] != -1) |
475 | close(g_cldPipe[0]); |
||
476 | if (g_cldPipe[1] != -1) |
||
477 | close(g_cldPipe[1]); |
||
47 | luk | 478 | } catch (InotifyException e) { |
479 | int err = e.GetErrorNumber(); |
||
480 | syslog(LOG_CRIT, "*** unhandled exception occurred ***"); |
||
481 | syslog(LOG_CRIT, " %s", e.GetMessage().c_str()); |
||
482 | syslog(LOG_CRIT, " error: (%i) %s", err, strerror(err)); |
||
69 | luk | 483 | ret = 1; |
45 | luk | 484 | } |
485 | |||
69 | luk | 486 | error: |
487 | |||
45 | luk | 488 | syslog(LOG_NOTICE, "stopping service"); |
489 | |||
490 | closelog(); |
||
491 | |||
69 | luk | 492 | return ret; |
45 | luk | 493 | } |