Subversion Repositories public

Rev

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
}