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