Subversion Repositories public

Rev

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