Subversion Repositories public

Rev

Rev 59 | Rev 63 | 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
 *
8
 * Copyright (C) 2006 Lukas Jelinek, <lukas@aiken.cz>
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
 
49 luk 33
/// Daemon yes/no
47 luk 34
#define DAEMON true
45 luk 35
 
49 luk 36
/// Logging options (console as fallback, log PID)
45 luk 37
#define INCRON_LOG_OPTS (LOG_CONS | LOG_PID)
49 luk 38
 
39
/// Logging facility (use CRON)
45 luk 40
#define INCRON_LOG_FACIL LOG_CRON
41
 
42
 
49 luk 43
/// User name to user table mapping definition
45 luk 44
typedef std::map<std::string, UserTable*> SUT_MAP;
45
 
49 luk 46
/// User name to user table mapping table
45 luk 47
SUT_MAP g_ut;
48
 
49 luk 49
/// Finish program yes/no
47 luk 50
volatile bool g_fFinish = false;
45 luk 51
 
61 luk 52
/// Pipe for notifying about dead children
53
int g_cldPipe[2];
49 luk 54
 
61 luk 55
// Buffer for emptying child pipe
56
#define CHILD_PIPE_BUF_LEN 32
57
char g_cldPipeBuf[CHILD_PIPE_BUF_LEN];
58
 
59
 
47 luk 60
/// Handles a signal.
61
/**
62
 * For SIGTERM and SIGINT it sets the program finish variable.
61 luk 63
 * For SIGCHLD it writes a character into the notification pipe
64
 * (this is a workaround made due to disability to reliably
65
 * wait for dead children).
47 luk 66
 *
67
 * \param[in] signo signal number
68
 */
45 luk 69
void on_signal(int signo)
70
{
61 luk 71
  switch (signo) {
72
    case SIGTERM:
73
    case SIGINT:
74
      g_fFinish = true;
75
      break;
76
    case SIGCHLD:
77
      // first empty pipe (to prevent internal buffer overflow)
78
      do {} while (read(g_cldPipe[0], g_cldPipeBuf, CHILD_PIPE_BUF_LEN) > 0);
79
 
80
      // now write one character
81
      write(g_cldPipe[1], "X", 1);
82
      break;
83
    default:;
84
  }
45 luk 85
}
86
 
47 luk 87
/// Checks whether an user exists and has permission to use incron.
88
/**
89
 * It searches for the given user name in the user database.
90
 * If it failes it returns 'false'. Otherwise it checks
91
 * permission files for this user (see InCronTab::CheckUser()).
92
 *
93
 * \param[in] user user name
94
 * \return true = user has permission to use incron, false = otherwise
95
 *
96
 * \sa InCronTab::CheckUser()
97
 */
45 luk 98
bool check_user(const char* user)
99
{
100
  struct passwd* pw = getpwnam(user);
101
  if (pw == NULL)
102
    return false;
103
 
104
  return InCronTab::CheckUser(user);
105
}
106
 
47 luk 107
/// Attempts to load all user incron tables.
108
/**
109
 * Loaded tables are registered for processing events.
110
 *
111
 * \param[in] pIn inotify object
112
 * \param[in] pEd inotify event dispatcher
113
 *
114
 * \throw InotifyException thrown if base table directory cannot be read
115
 */
116
void load_tables(Inotify* pIn, EventDispatcher* pEd) throw (InotifyException)
45 luk 117
{
118
  DIR* d = opendir(INCRON_TABLE_BASE);
47 luk 119
  if (d == NULL)
120
    throw InotifyException("cannot open table directory", errno);
45 luk 121
 
122
  syslog(LOG_NOTICE, "loading user tables");
123
 
124
  struct dirent* pDe = NULL;
125
  while ((pDe = readdir(d)) != NULL) {
126
    std::string un(pDe->d_name);
59 luk 127
 
128
    bool ok = pDe->d_type == DT_REG;
129
    if (pDe->d_type == DT_UNKNOWN) {
130
      struct stat st;
131
      if (stat(pDe->d_name, &st) == 0)
132
        ok = S_ISREG(st.st_mode);
133
    }
134
 
135
    if (ok) {
45 luk 136
      if (check_user(pDe->d_name)) {
137
        syslog(LOG_INFO, "loading table for user %s", pDe->d_name);
138
        UserTable* pUt = new UserTable(pIn, pEd, un);
139
        g_ut.insert(SUT_MAP::value_type(un, pUt));
140
        pUt->Load();
141
      }
142
      else {
143
        syslog(LOG_WARNING, "table for invalid user %s found (ignored)", pDe->d_name);
144
      }
145
    }
146
  }
147
 
148
  closedir(d);
149
}
150
 
61 luk 151
/// Prepares a 'dead/done child' notification pipe.
152
/**
153
 * This function returns no value at all and on error it
154
 * throws an exception.
155
 */
156
void prepare_pipe()
157
{
158
  g_cldPipe[0] = -1;
159
  g_cldPipe[1] = -1;
160
 
161
  if (pipe(g_cldPipe) != 0)
162
      throw InotifyException("cannot create notification pipe", errno, NULL);
163
 
164
  for (int i=0; i<2; i++) {
165
    int res = fcntl(g_cldPipe[i], F_GETFL);
166
    if (res == -1)
167
      throw InotifyException("cannot get pipe flags", errno, NULL);
168
 
169
    res |= O_NONBLOCK;
170
 
171
    if (fcntl(g_cldPipe[i], F_SETFL, res) == -1)
172
      throw InotifyException("cannot set pipe flags", errno, NULL);
173
  }
174
}
175
 
47 luk 176
/// Main application function.
177
/**
178
 * \param[in] argc argument count
179
 * \param[in] argv argument array
180
 * \return 0 on success, 1 on error
181
 *
182
 * \attention In daemon mode, it finishes immediately.
183
 */
45 luk 184
int main(int argc, char** argv)
185
{
55 luk 186
  openlog(INCRON_DAEMON_NAME, INCRON_LOG_OPTS, INCRON_LOG_FACIL);
45 luk 187
 
55 luk 188
  syslog(LOG_NOTICE, "starting service (version %s, built on %s %s)", INCRON_VERSION, __DATE__, __TIME__);
45 luk 189
 
47 luk 190
  try {
191
    Inotify in;
192
    in.SetNonBlock(true);
193
 
194
    EventDispatcher ed(&in);
195
 
196
    try {
197
      load_tables(&in, &ed);
198
    } catch (InotifyException e) {
199
      int err = e.GetErrorNumber();
200
      syslog(LOG_CRIT, "%s: (%i) %s", e.GetMessage().c_str(), err, strerror(err));
201
      syslog(LOG_NOTICE, "stopping service");
202
      closelog();
203
      return 1;
204
    }
205
 
61 luk 206
    if (DAEMON)
207
      daemon(0, 0);
208
 
209
    prepare_pipe();
210
 
47 luk 211
    signal(SIGTERM, on_signal);
212
    signal(SIGINT, on_signal);
213
    signal(SIGCHLD, on_signal);
214
 
215
    uint32_t wm = IN_CLOSE_WRITE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_UNMOUNT;
216
    InotifyWatch watch(INCRON_TABLE_BASE, wm);
217
    in.Add(watch);
218
 
219
    syslog(LOG_NOTICE, "ready to process filesystem events");
220
 
221
    InotifyEvent e;
222
 
61 luk 223
    struct pollfd pfd[2];
224
    pfd[0].fd = in.GetDescriptor();
225
    pfd[0].events = (short) POLLIN;
226
    pfd[0].revents = (short) 0;
227
    pfd[1].fd = g_cldPipe[0];
228
    pfd[1].events = (short) POLLIN;
229
    pfd[1].revents = (short) 0;
47 luk 230
 
231
    while (!g_fFinish) {
232
 
61 luk 233
      int res = poll(pfd, 2, -1);
234
 
47 luk 235
      if (res > 0) {
61 luk 236
        if (pfd[1].revents & ((short) POLLIN)) {
237
          char c;
238
          while (read(pfd[1].fd, &c, 1) > 0) {}
239
          UserTable::FinishDone();
240
        }
241
        else {
242
          in.WaitForEvents(true);
243
        }
47 luk 244
      }
245
      else if (res < 0) {
246
        if (errno != EINTR)
247
          throw InotifyException("polling failed", errno, NULL);
248
      }
249
 
45 luk 250
      while (in.GetEvent(e)) {
251
 
252
        if (e.GetWatch() == &watch) {
253
          if (e.IsType(IN_DELETE_SELF) || e.IsType(IN_UNMOUNT)) {
254
            syslog(LOG_CRIT, "base directory destroyed, exitting");
255
            g_fFinish = true;
256
          }
257
          else if (!e.GetName().empty()) {
258
            SUT_MAP::iterator it = g_ut.find(e.GetName());
259
            if (it != g_ut.end()) {
260
              UserTable* pUt = (*it).second;
261
              if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
262
                syslog(LOG_INFO, "table for user %s changed, reloading", e.GetName().c_str());
263
                pUt->Dispose();
264
                pUt->Load();
265
              }
266
              else if (e.IsType(IN_MOVED_FROM) || e.IsType(IN_DELETE)) {
267
                syslog(LOG_INFO, "table for user %s destroyed, removing", e.GetName().c_str());
268
                delete pUt;
269
                g_ut.erase(it);
270
              }
271
            }
272
            else if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
273
              if (check_user(e.GetName().c_str())) {
274
                syslog(LOG_INFO, "table for user %s created, loading", e.GetName().c_str());
275
                UserTable* pUt = new UserTable(&in, &ed, e.GetName());
276
                g_ut.insert(SUT_MAP::value_type(e.GetName(), pUt));
277
                pUt->Load();
278
              }
279
            }
280
          }
281
        }
282
        else {
283
          ed.DispatchEvent(e);
284
        }
61 luk 285
 
45 luk 286
      }
287
    }
61 luk 288
 
289
    if (g_cldPipe[0] != -1)
290
      close(g_cldPipe[0]);
291
    if (g_cldPipe[1] != -1)
292
      close(g_cldPipe[1]);
47 luk 293
  } catch (InotifyException e) {
294
    int err = e.GetErrorNumber();
295
    syslog(LOG_CRIT, "*** unhandled exception occurred ***");
296
    syslog(LOG_CRIT, "  %s", e.GetMessage().c_str());
297
    syslog(LOG_CRIT, "  error: (%i) %s", err, strerror(err));
45 luk 298
  }
299
 
300
  syslog(LOG_NOTICE, "stopping service");
301
 
302
  closelog();
303
 
304
  return 0;
305
}