Subversion Repositories public

Rev

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