Rev 71 | Rev 75 | Go to most recent revision | 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 | |||
47 | luk | 111 | /// Attempts to load all user incron tables. |
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 | |||
61 | luk | 194 | /// Prepares a 'dead/done child' notification pipe. |
195 | /** |
||
196 | * This function returns no value at all and on error it |
||
197 | * throws an exception. |
||
198 | */ |
||
199 | void prepare_pipe() |
||
200 | { |
||
201 | g_cldPipe[0] = -1; |
||
202 | g_cldPipe[1] = -1; |
||
203 | |||
204 | if (pipe(g_cldPipe) != 0) |
||
205 | throw InotifyException("cannot create notification pipe", errno, NULL); |
||
206 | |||
207 | for (int i=0; i<2; i++) { |
||
208 | int res = fcntl(g_cldPipe[i], F_GETFL); |
||
209 | if (res == -1) |
||
210 | throw InotifyException("cannot get pipe flags", errno, NULL); |
||
211 | |||
212 | res |= O_NONBLOCK; |
||
213 | |||
214 | if (fcntl(g_cldPipe[i], F_SETFL, res) == -1) |
||
215 | throw InotifyException("cannot set pipe flags", errno, NULL); |
||
67 | luk | 216 | |
217 | res = fcntl(g_cldPipe[i], F_GETFD); |
||
218 | if (res == -1) |
||
219 | throw InotifyException("cannot get pipe flags", errno, NULL); |
||
220 | |||
221 | res |= FD_CLOEXEC; |
||
222 | |||
223 | if (fcntl(g_cldPipe[i], F_SETFD, res) == -1) |
||
224 | throw InotifyException("cannot set pipe flags", errno, NULL); |
||
61 | luk | 225 | } |
226 | } |
||
227 | |||
67 | luk | 228 | /// Checks whether a parameter string is a specific command. |
229 | /** |
||
230 | * The string is accepted if it equals either the short or long |
||
231 | * form of the command. |
||
232 | * |
||
233 | * \param[in] s checked string |
||
234 | * \param[in] shortCmd short form of command |
||
235 | * \param[in] longCmd long form of command |
||
236 | * \return true = string accepted, false = otherwise |
||
237 | */ |
||
238 | bool check_parameter(const char* s, const char* shortCmd, const char* longCmd) |
||
239 | { |
||
240 | return strcmp(s, shortCmd) == 0 |
||
241 | || strcmp(s, longCmd) == 0; |
||
242 | } |
||
243 | |||
244 | /// Initializes a poll array. |
||
245 | /** |
||
69 | luk | 246 | * \param[out] pfd poll structure array |
67 | luk | 247 | * \param[in] pipefd pipe file descriptor |
248 | * \param[in] infd inotify infrastructure file descriptor |
||
249 | */ |
||
250 | void init_poll_array(struct pollfd pfd[], int pipefd, int infd) |
||
251 | { |
||
252 | pfd[0].fd = pipefd; |
||
253 | pfd[0].events = (short) POLLIN; |
||
254 | pfd[0].revents = (short) 0; |
||
255 | pfd[1].fd = infd; |
||
256 | pfd[1].events = (short) POLLIN; |
||
257 | pfd[1].revents = (short) 0; |
||
258 | } |
||
259 | |||
260 | |||
47 | luk | 261 | /// Main application function. |
262 | /** |
||
263 | * \param[in] argc argument count |
||
264 | * \param[in] argv argument array |
||
265 | * \return 0 on success, 1 on error |
||
266 | * |
||
267 | * \attention In daemon mode, it finishes immediately. |
||
268 | */ |
||
45 | luk | 269 | int main(int argc, char** argv) |
270 | { |
||
69 | luk | 271 | AppArgs::Init(); |
272 | |||
273 | if (!( AppArgs::AddOption("about", '?', AAT_NO_VALUE, false) |
||
274 | && AppArgs::AddOption("help", 'h', AAT_NO_VALUE, false) |
||
275 | && AppArgs::AddOption("foreground", 'n', AAT_NO_VALUE, false) |
||
276 | && AppArgs::AddOption("kill", 'k', AAT_NO_VALUE, false) |
||
277 | && AppArgs::AddOption("config", 'f', AAT_MANDATORY_VALUE, false)) |
||
278 | && AppArgs::AddOption("version", 'V', AAT_NO_VALUE, false)) |
||
279 | { |
||
280 | fprintf(stderr, "error while initializing application"); |
||
67 | luk | 281 | return 1; |
282 | } |
||
283 | |||
69 | luk | 284 | AppArgs::Parse(argc, argv); |
285 | |||
286 | if (AppArgs::ExistsOption("help")) { |
||
287 | fprintf(stderr, "%s\n", INCROND_HELP); |
||
288 | return 0; |
||
289 | } |
||
290 | |||
291 | if (AppArgs::ExistsOption("about")) { |
||
292 | fprintf(stderr, "%s\n", INCROND_DESCRIPTION); |
||
293 | return 0; |
||
294 | } |
||
295 | |||
296 | if (AppArgs::ExistsOption("version")) { |
||
297 | fprintf(stderr, "%s\n", INCROND_VERSION); |
||
298 | return 0; |
||
299 | } |
||
300 | |||
301 | IncronCfg::Init(); |
||
302 | |||
71 | luk | 303 | std::string cfg; |
304 | if (!AppArgs::GetOption("config", cfg)) |
||
305 | cfg = INCRON_CONFIG; |
||
306 | IncronCfg::Load(cfg); |
||
307 | |||
308 | std::string lckdir; |
||
309 | IncronCfg::GetValue("lockfile_dir", lckdir); |
||
310 | std::string lckfile; |
||
311 | IncronCfg::GetValue("lockfile_name", lckfile); |
||
312 | AppInstance app(lckfile, lckdir); |
||
313 | |||
69 | luk | 314 | if (AppArgs::ExistsOption("kill")) { |
315 | fprintf(stderr, "attempting to terminate a running instance of incrond...\n"); |
||
73 | luk | 316 | if (app.Terminate()) { |
69 | luk | 317 | fprintf(stderr, "instance(s) notified, going down\n"); |
67 | luk | 318 | return 0; |
319 | } |
||
69 | luk | 320 | else { |
321 | fprintf(stderr, "error - incrond probably not running\n"); |
||
67 | luk | 322 | return 1; |
323 | } |
||
324 | } |
||
325 | |||
69 | luk | 326 | if (AppArgs::ExistsOption("foreground")) |
327 | g_daemon = false; |
||
45 | luk | 328 | |
69 | luk | 329 | |
330 | openlog(INCROND_NAME, INCRON_LOG_OPTS, INCRON_LOG_FACIL); |
||
331 | |||
55 | luk | 332 | syslog(LOG_NOTICE, "starting service (version %s, built on %s %s)", INCRON_VERSION, __DATE__, __TIME__); |
45 | luk | 333 | |
69 | luk | 334 | AppArgs::Destroy(); |
335 | |||
336 | int ret = 0; |
||
337 | |||
338 | std::string sysBase; |
||
339 | std::string userBase; |
||
340 | |||
341 | if (!IncronCfg::GetValue("system_table_dir", sysBase)) |
||
342 | throw InotifyException("configuration is corrupted", EINVAL); |
||
343 | |||
344 | if (access(sysBase.c_str(), R_OK) != 0) { |
||
345 | syslog(LOG_CRIT, "cannot read directory for system tables (%s): (%i) %s", sysBase.c_str(), errno, strerror(errno)); |
||
346 | if (!g_daemon) |
||
347 | fprintf(stderr, "cannot read directory for system tables (%s): (%i) %s", sysBase.c_str(), errno, strerror(errno)); |
||
348 | ret = 1; |
||
349 | goto error; |
||
350 | } |
||
351 | |||
352 | if (!IncronCfg::GetValue("user_table_dir", userBase)) |
||
353 | throw InotifyException("configuration is corrupted", EINVAL); |
||
354 | |||
355 | if (access(userBase.c_str(), R_OK) != 0) { |
||
356 | syslog(LOG_CRIT, "cannot read directory for user tables (%s): (%i) %s", userBase.c_str(), errno, strerror(errno)); |
||
357 | if (!g_daemon) |
||
358 | fprintf(stderr, "cannot read directory for user tables (%s): (%i) %s", userBase.c_str(), errno, strerror(errno)); |
||
359 | ret = 1; |
||
360 | goto error; |
||
361 | } |
||
362 | |||
47 | luk | 363 | try { |
67 | luk | 364 | if (g_daemon) |
365 | daemon(0, 0); |
||
69 | luk | 366 | |
367 | try { |
||
368 | if (!app.Lock()) { |
||
369 | syslog(LOG_CRIT, "another instance of incrond already running"); |
||
370 | if (!g_daemon) |
||
371 | fprintf(stderr, "another instance of incrond already running\n"); |
||
372 | ret = 1; |
||
373 | goto error; |
||
374 | } |
||
375 | } catch (AppInstException e) { |
||
376 | syslog(LOG_CRIT, "instance lookup failed: (%i) %s", e.GetErrorNumber(), strerror(e.GetErrorNumber())); |
||
377 | if (!g_daemon) |
||
378 | fprintf(stderr, "instance lookup failed: (%i) %s\n", e.GetErrorNumber(), strerror(e.GetErrorNumber())); |
||
379 | ret = 1; |
||
380 | goto error; |
||
381 | } |
||
67 | luk | 382 | |
383 | prepare_pipe(); |
||
384 | |||
47 | luk | 385 | Inotify in; |
386 | in.SetNonBlock(true); |
||
67 | luk | 387 | in.SetCloseOnExec(true); |
47 | luk | 388 | |
67 | luk | 389 | uint32_t wm = IN_CLOSE_WRITE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_UNMOUNT; |
69 | luk | 390 | InotifyWatch stw(sysBase, wm); |
67 | luk | 391 | in.Add(stw); |
69 | luk | 392 | InotifyWatch utw(userBase, wm); |
67 | luk | 393 | in.Add(utw); |
47 | luk | 394 | |
67 | luk | 395 | EventDispatcher ed(g_cldPipe[0], &in, &stw, &utw); |
396 | |||
47 | luk | 397 | try { |
67 | luk | 398 | load_tables(&ed); |
47 | luk | 399 | } catch (InotifyException e) { |
400 | int err = e.GetErrorNumber(); |
||
401 | syslog(LOG_CRIT, "%s: (%i) %s", e.GetMessage().c_str(), err, strerror(err)); |
||
69 | luk | 402 | ret = 1; |
403 | goto error; |
||
47 | luk | 404 | } |
405 | |||
406 | signal(SIGTERM, on_signal); |
||
407 | signal(SIGINT, on_signal); |
||
408 | signal(SIGCHLD, on_signal); |
||
409 | |||
410 | syslog(LOG_NOTICE, "ready to process filesystem events"); |
||
411 | |||
412 | while (!g_fFinish) { |
||
413 | |||
67 | luk | 414 | int res = poll(ed.GetPollData(), ed.GetSize(), -1); |
61 | luk | 415 | |
47 | luk | 416 | if (res > 0) { |
67 | luk | 417 | if (ed.ProcessEvents()) |
61 | luk | 418 | UserTable::FinishDone(); |
47 | luk | 419 | } |
420 | else if (res < 0) { |
||
421 | if (errno != EINTR) |
||
422 | throw InotifyException("polling failed", errno, NULL); |
||
423 | } |
||
424 | |||
45 | luk | 425 | } |
61 | luk | 426 | |
427 | if (g_cldPipe[0] != -1) |
||
428 | close(g_cldPipe[0]); |
||
429 | if (g_cldPipe[1] != -1) |
||
430 | close(g_cldPipe[1]); |
||
47 | luk | 431 | } catch (InotifyException e) { |
432 | int err = e.GetErrorNumber(); |
||
433 | syslog(LOG_CRIT, "*** unhandled exception occurred ***"); |
||
434 | syslog(LOG_CRIT, " %s", e.GetMessage().c_str()); |
||
435 | syslog(LOG_CRIT, " error: (%i) %s", err, strerror(err)); |
||
69 | luk | 436 | ret = 1; |
45 | luk | 437 | } |
438 | |||
69 | luk | 439 | error: |
440 | |||
45 | luk | 441 | syslog(LOG_NOTICE, "stopping service"); |
442 | |||
443 | closelog(); |
||
444 | |||
69 | luk | 445 | return ret; |
45 | luk | 446 | } |