Subversion Repositories public

Rev

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
}