Subversion Repositories public

Compare Revisions

Ignore whitespace Rev 66 → Rev 67

/incron/trunk/incrontab.5
1,9 → 1,13
.TH "incrontab" "5" "0.3.5" "Lukas Jelinek" "incron documentation"
.TH "incrontab" "5" "0.4.0" "Lukas Jelinek" "incron documentation"
.SH "NAME"
incrontab \- tables for driving inotify cron (incron)
.SH "DESCRIPTION"
An incrontab file contains instructions to the \fIincrond\fR(8) daemon of the general form: "run this command on these file events". Each user has their own incrontab, and commands in any given incrontab will be executed as the user who owns the incrontab. System users (such as apache, postfix, nobody etc.) may have their own incrontab.
An incrontab file contains instructions to the \fIincrond\fR(8) daemon of the general form: "run this command on these file events". There are two categories of tables: system tables (with root privileges) and user tables (with user privileges).
 
System tables are located in /etc/incron.d and may have any names. Each system table exists separately inside incron and their watches never collide.
 
Each user has their own table, and commands in any given incrontab will be executed as the user who owns the incrontab. System users (such as apache, postfix, nobody etc.) may have their own incrontab.
 
incrontab files are read when the \fIincrond\fR(8) daemon starts and after any change (incrontab file are being hooked when incrond is running).
 
Blank lines are ignored. The general line format is the following:
12,7 → 16,7
 
Where \fIpath\fR is an absolute filesystem path, \fImask\fR is an event mask (in symbolic or numeric form) and \fIcommand\fR is an executable file (or a script) with its arguments. The executable file may be noted as an absolute path or only as the name itself (PATH locations are examined).
 
Please remember that the same path may occur only once (otherwise the behavior is undefined).
Please remember that the same path may occur only once per table (otherwise only the first occurrence takes effect and an error message is emitted to the system log).
.SH "EXAMPLE"
These are some example rules which can be used in an incrontab file:
 
31,6 → 35,7
The third example is used for monitoring the /home directory for newly create files or directories (it practically means an event is sent when a new user is added). This event is processed by a program specified by an absolute path.
 
And the final line shows how to use numeric event mask instead of textual one. The value 12 is exactly the same as IN_ATTRIB,IN_CLOSE_WRITE.
 
.SH "SEE ALSO"
incrond(8), incrontab(1)
.SH "AUTHOR"
/incron/trunk/ict-main.cpp
21,10 → 21,13
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/inotify.h>
 
#include "incron.h"
#include "incrontab.h"
 
// Alternative editor
#define INCRON_ALT_EDITOR "/etc/alternatives/editor"
 
// #define INCRON_DEFAULT_EDITOR "nano" // for vim haters like me ;-)
#define INCRON_DEFAULT_EDITOR "vim"
41,6 → 44,7
{"list", 'l', 0, 0, "List the current table" },
{"remove", 'r', 0, 0, "Remove the table completely" },
{"edit", 'e', 0, 0, "Edit the table" },
{"types", 't', 0, 0, "List all supported event types" },
{"user", 'u', "USER", 0, "Override the current user" },
{ 0 }
};
51,7 → 55,8
OPER_NONE, /// nothing
OPER_LIST, /// list table
OPER_REMOVE, /// remove table
OPER_EDIT /// edit table
OPER_EDIT, /// edit table
OPER_TYPES /// list event types
} InCronTab_Operation_t;
 
/// incrontab arguments
83,6 → 88,9
case 'e':
arguments->oper = OPER_EDIT;
break;
case 't':
arguments->oper = OPER_TYPES;
break;
case 'u':
arguments->user = arg;
break;
157,7 → 165,7
{
std::string tp(InCronTab::GetUserTablePath(user));
if (eaccess(tp.c_str(), R_OK) != 0) {
if (euidaccess(tp.c_str(), R_OK) != 0) {
if (errno == ENOENT) {
fprintf(stderr, "no table for %s\n", user);
return true;
270,12 → 278,25
goto end;
}
mt = st.st_mtime;
mt = st.st_mtime; // save modification time for detecting its change
// Editor selecting algorithm:
// 1. Check EDITOR environment variable
// 2. Check VISUAL environment variable
// 3. Check presence of /etc/alternatives/editor
// 4. Use hard-wired editor
e = getenv("EDITOR");
if (e == NULL)
e = INCRON_DEFAULT_EDITOR;
if (e == NULL) {
e = getenv("VISUAL");
if (e == NULL) {
if (access(INCRON_ALT_EDITOR, X_OK) == 0)
e = INCRON_ALT_EDITOR;
else
e = INCRON_DEFAULT_EDITOR;
}
}
// this block is explicite due to gotos' usage simplification
{
pid_t pid = fork();
if (pid == 0) {
339,6 → 360,30
}
 
 
/// Prints the list of all available inotify event types.
void list_types()
{
printf( "IN_ACCESS,IN_MODIFY,IN_ATTRIB,IN_CLOSE_WRITE,"\
"IN_CLOSE_NOWRITE,IN_OPEN,IN_MOVED_FROM,IN_MOVED_TO,"\
"IN_CREATE,IN_DELETE,IN_DELETE_SELF,IN_CLOSE,IN_MOVE,"\
"IN_ONESHOT,IN_ALL_EVENTS");
#ifdef IN_DONT_FOLLOW
printf(",IN_DONT_FOLLOW");
#endif // IN_DONT_FOLLOW
 
#ifdef IN_ONLYDIR
printf(",IN_ONLYDIR");
#endif // IN_ONLYDIR
 
#ifdef IN_MOVE_SELF
printf(",IN_MOVE_SELF");
#endif // IN_MOVE_SELF
printf("\n");
}
 
 
int main(int argc, char** argv)
{
struct arguments arguments;
405,6 → 450,9
if (!edit_table(arguments.user))
return 1;
break;
case OPER_TYPES:
list_types();
break;
default:
fprintf(stderr, "invalid usage\n");
return 1;
/incron/trunk/CHANGELOG
1,3 → 1,15
0.4.0 2007-01-13
* based on inotify-cxx 0.7.0
* incrontab has a feature to find out supported event types (--types or -t)
* incrond now supports command line arguments
* incrond can be run on foreground (--foreground, -n)
* incrond can be simply stopped (--kill, -k)
* support for system tables (/etc/incron.d) added
* eaccess() replaced by euiaccess() (#0000125)
* each table now use its own inotify descriptor (#0000129)
* descriptors are closed on executing child processes
 
 
0.3.5 2007-01-09
* based on inotify-cxx 0.6.3
* a security bug related to access rights to watched files fixed (#0000119)
/incron/trunk/README
84,7 → 84,11
 
/tmp IN_ALL_EVENTS efg $@/$# $&
 
Since 0.4.0 also system tables are supported. They are located in
/etc/incron.d and their commands use root privileges. System tables
are intended to be changed directly (without incrontab).
 
 
5. Bugs, suggestions
THIS PROGRAM IS AN ALPHA VERSION. IT PROBABLY CONTAINS BUGS AND
THEREFORE IT IS NOT INTENDED FOR PRODUCTION USE.
/incron/trunk/incrontab.cpp
223,10 → 223,17
 
std::string InCronTab::GetUserTablePath(const std::string& rUser)
{
std::string s(INCRON_TABLE_BASE);
std::string s(INCRON_USER_TABLE_BASE);
s.append(rUser);
return s;
}
 
std::string InCronTab::GetSystemTablePath(const std::string& rName)
{
std::string s(INCRON_SYS_TABLE_BASE);
s.append(rName);
return s;
}
 
 
 
/incron/trunk/incrond.8
1,23 → 1,32
.TH "incrond" "8" "0.3.5" "Lukas Jelinek" "incron documentation"
.TH "incrond" "8" "0.4.0" "Lukas Jelinek" "incron documentation"
.SH "NAME"
incrond \- inotify cron (incron) daemon
 
.SH "SYNOPSIS"
\fBincrond\fR
\fBincrond\fR [ \fB\-n\fR | \fB\-k\fR ]
.SH "DESCRIPTION"
The inotify cron daemon (\fIincrond\fR) is a daemon which monitors filesystem events and executes commands defined in user tables. It's use is generally similar to \fIcron\fR(8).
The inotify cron daemon (\fIincrond\fR) is a daemon which monitors filesystem events and executes commands defined in system and user tables. It's use is generally similar to \fIcron\fR(8).
 
\fIincrond\fR can be started from /etc/rc, /etc/rc.local and so on. It daemonizes itself (returns immediately) and doesn't need to be started with & and through \fInohup\fR(1). Running on foreground is currently not possible but it will be implemented in the future.
\fIincrond\fR can be started from /etc/rc, /etc/rc.local and so on. It daemonizes itself (returns immediately) and doesn't need to be started with & and through \fInohup\fR(1). It can be run on foreground too.
 
\fIincrond\fR searches /var/spool/incron for \fIincrontab\fR(5) files named after user accounts. If a table (incrontab) is changed \fIincrond\fR reacts immediately and reloads the table. Currently running subprocesses (commands) are not affected.
\fIincrond\fR uses two categories of tables \fIincrontab\fR(5). System tables are located in /etc/incron.d and are maintained outside of incron (e.g. by various applications). These tables work on root rights level and thus any file may be watched and commands are executed with root privileges.
 
User tables are located in /var/spool/incron and have names based on user accounts. These tables use users' access rights, thus only files which the user may access are watched. Commands are executed with users' privileges.
 
If a table (incrontab) is changed \fIincrond\fR reacts immediately and reloads the table. Currently running child processes (commands) are not affected.
 
There are two files determining whether an user is allowed to use incron. These files have very simple syntax \- one user name per line. If /etc/incron.allow exists the user must be noted there to be allowed to use incron. Otherwise if /etc/incron.deny exists the user must not be noted there to use incron. If none of these files exists there is no other restriction whether anybody may use incron.
 
The daemon itself is currently not protected against looping. If a command executed due to an event causes the same event it leads to an infinite loop unless a flag mask containing IN_NO_LOOP is specified. Please beware of this and do not allow permission for use incron to unreliable users.
 
 
\fB\-n\fR (or \fB\-\-foreground\fR) option causes running on foreground. This is useful especially for testing, debugging and optimization.
 
\fB\-k\fR (or \fB\-\-kill\fR) option terminates all running instances of \fBincrond\fR. It takes effect only on instances of the same executable (using the same file).
.SH "SEE ALSO"
incrontab(1), incrontab(5)
.SH "BUGS"
incrond is currently not resistent against looping.
incrond is currently not resistent against looping. Recursive monitoring (whole subtrees) has not been implemented yet.
.SH "AUTHOR"
Lukas Jelinek <lukas@aiken.cz>
.SH "COPYING"
/incron/trunk/icd-main.cpp
30,8 → 30,6
#include "incrontab.h"
#include "usertable.h"
 
/// Daemon yes/no
#define DAEMON true
 
/// Logging options (console as fallback, log PID)
#define INCRON_LOG_OPTS (LOG_CONS | LOG_PID)
39,10 → 37,19
/// Logging facility (use CRON)
#define INCRON_LOG_FACIL LOG_CRON
 
/// Help text
#define INCROND_HELP_TEXT "Usage: incrond [option]\n" \
"incrond - inotify cron daemon\n" \
"(c) Lukas Jelinek, 2006, 2007\n\n" \
"-h, --help Give this help list\n" \
"-n, --foreground Run on foreground (don't daemonize)\n" \
"-k, --kill Terminate running instance of incrond\n" \
"-V, --version Print program version\n\n" \
"Report bugs to <bugs@aiken.cz>"
 
/// User name to user table mapping definition
typedef std::map<std::string, UserTable*> SUT_MAP;
#define INCROND_VERSION_TEXT INCRON_DAEMON_NAME " " INCRON_VERSION
 
 
/// User name to user table mapping table
SUT_MAP g_ut;
 
56,6 → 63,8
#define CHILD_PIPE_BUF_LEN 32
char g_cldPipeBuf[CHILD_PIPE_BUF_LEN];
 
/// Daemonize true/false
bool g_daemon = true;
 
/// Handles a signal.
/**
84,40 → 93,52
}
}
 
/// Checks whether an user exists and has permission to use incron.
/**
* It searches for the given user name in the user database.
* If it failes it returns 'false'. Otherwise it checks
* permission files for this user (see InCronTab::CheckUser()).
*
* \param[in] user user name
* \return true = user has permission to use incron, false = otherwise
*
* \sa InCronTab::CheckUser()
*/
bool check_user(const char* user)
{
struct passwd* pw = getpwnam(user);
if (pw == NULL)
return false;
return InCronTab::CheckUser(user);
}
 
/// Attempts to load all user incron tables.
/**
* Loaded tables are registered for processing events.
*
* \param[in] pIn inotify object
* \param[in] pEd inotify event dispatcher
*
* \throw InotifyException thrown if base table directory cannot be read
*/
void load_tables(Inotify* pIn, EventDispatcher* pEd) throw (InotifyException)
void load_tables(EventDispatcher* pEd) throw (InotifyException)
{
DIR* d = opendir(INCRON_TABLE_BASE);
// WARNING - this function has not been optimized!!!
DIR* d = opendir(INCRON_SYS_TABLE_BASE);
if (d != NULL) {
syslog(LOG_NOTICE, "loading system tables");
struct dirent* pDe = NULL;
while ((pDe = readdir(d)) != NULL) {
std::string un(pDe->d_name);
bool ok = pDe->d_type == DT_REG;
if (pDe->d_type == DT_UNKNOWN) {
struct stat st;
if (stat(pDe->d_name, &st) == 0)
ok = S_ISREG(st.st_mode);
}
if (ok) {
syslog(LOG_INFO, "loading table %s", pDe->d_name);
UserTable* pUt = new UserTable(pEd, un, true);
g_ut.insert(SUT_MAP::value_type(std::string(INCRON_SYS_TABLE_BASE) + un, pUt));
pUt->Load();
}
}
closedir(d);
}
else {
syslog(LOG_WARNING, "cannot open system table directory (ignoring)");
}
d = opendir(INCRON_USER_TABLE_BASE);
if (d == NULL)
throw InotifyException("cannot open table directory", errno);
throw InotifyException("cannot open user table directory", errno);
syslog(LOG_NOTICE, "loading user tables");
133,10 → 154,10
}
if (ok) {
if (check_user(pDe->d_name)) {
if (UserTable::CheckUser(pDe->d_name)) {
syslog(LOG_INFO, "loading table for user %s", pDe->d_name);
UserTable* pUt = new UserTable(pIn, pEd, un);
g_ut.insert(SUT_MAP::value_type(un, pUt));
UserTable* pUt = new UserTable(pEd, un, false);
g_ut.insert(SUT_MAP::value_type(std::string(INCRON_USER_TABLE_BASE) + un, pUt));
pUt->Load();
}
else {
170,9 → 191,113
if (fcntl(g_cldPipe[i], F_SETFL, res) == -1)
throw InotifyException("cannot set pipe flags", errno, NULL);
res = fcntl(g_cldPipe[i], F_GETFD);
if (res == -1)
throw InotifyException("cannot get pipe flags", errno, NULL);
res |= FD_CLOEXEC;
if (fcntl(g_cldPipe[i], F_SETFD, res) == -1)
throw InotifyException("cannot set pipe flags", errno, NULL);
}
}
 
/// Checks whether a parameter string is a specific command.
/**
* The string is accepted if it equals either the short or long
* form of the command.
*
* \param[in] s checked string
* \param[in] shortCmd short form of command
* \param[in] longCmd long form of command
* \return true = string accepted, false = otherwise
*/
bool check_parameter(const char* s, const char* shortCmd, const char* longCmd)
{
return strcmp(s, shortCmd) == 0
|| strcmp(s, longCmd) == 0;
}
 
/// Attempts to kill all running instances of incrond.
/**
* It kills only instances which use the same executable image
* as the currently running one.
*
* \return true = at least one instance killed, false = otherwise
* \attention More than one instance may be currently run simultaneously.
*/
bool kill_incrond()
{
unsigned pid_self = (unsigned) getpid(); // self PID
char s[PATH_MAX];
snprintf(s, PATH_MAX, "/proc/%u/exe", pid_self);
char path_self[PATH_MAX];
ssize_t len = readlink(s, path_self, PATH_MAX-1);
if (len <= 0)
return false;
path_self[len] = '\0';
DIR* d = opendir("/proc");
if (d == NULL)
return false;
bool ok = false;
char path[PATH_MAX];
struct dirent* de = NULL;
while ((de = readdir(d)) != NULL) {
bool to = false;
if (de->d_type == DT_DIR)
to = true;
else if (de->d_type == DT_UNKNOWN) {
// fallback way
snprintf(s, PATH_MAX, "/proc/%s", de->d_name);
struct stat st;
if (stat(s, &st) == 0 && S_ISDIR(st.st_mode))
to = true;
}
if (to) {
unsigned pid;
if (sscanf(de->d_name, "%u", &pid) == 1 // PID successfully retrieved
&& pid != pid_self) // don't do suicide!
{
snprintf(s, PATH_MAX, "/proc/%u/exe", pid);
len = readlink(s, path, PATH_MAX-1);
if (len > 0) {
path[len] = '\0';
if ( strcmp(path, path_self) == 0
&& kill((pid_t) pid, SIGTERM) == 0)
ok = true;
}
}
}
}
closedir(d);
return ok;
}
 
/// Initializes a poll array.
/**
* \param[in] pipefd pipe file descriptor
* \param[in] infd inotify infrastructure file descriptor
*/
void init_poll_array(struct pollfd pfd[], int pipefd, int infd)
{
pfd[0].fd = pipefd;
pfd[0].events = (short) POLLIN;
pfd[0].revents = (short) 0;
pfd[1].fd = infd;
pfd[1].events = (short) POLLIN;
pfd[1].revents = (short) 0;
}
 
 
/// Main application function.
/**
* \param[in] argc argument count
183,18 → 308,65
*/
int main(int argc, char** argv)
{
if (argc > 2) {
fprintf(stderr, "error: too many parameters\n");
fprintf(stderr, "give --help or -h for more information\n");
return 1;
}
if (argc == 2) {
if (check_parameter(argv[1], "-h", "--help")) {
printf("%s\n", INCROND_HELP_TEXT);
return 0;
}
else if (check_parameter(argv[1], "-n", "--foreground")) {
g_daemon = false;
}
else if (check_parameter(argv[1], "-k", "--kill")) {
fprintf(stderr, "attempting to terminate a running instance of incrond...\n");
if (kill_incrond()) {
fprintf(stderr, "instance(s) notified, going down\n");
return 0;
}
else {
fprintf(stderr, "error - incrond probably not running\n");
return 1;
}
}
else if (check_parameter(argv[1], "-V", "--version")) {
printf("%s\n", INCROND_VERSION_TEXT);
return 0;
}
else {
fprintf(stderr, "error - unrecognized parameter: %s\n", argv[1]);
return 1;
}
}
openlog(INCRON_DAEMON_NAME, INCRON_LOG_OPTS, INCRON_LOG_FACIL);
syslog(LOG_NOTICE, "starting service (version %s, built on %s %s)", INCRON_VERSION, __DATE__, __TIME__);
try {
if (g_daemon)
daemon(0, 0);
prepare_pipe();
Inotify in;
in.SetNonBlock(true);
in.SetCloseOnExec(true);
EventDispatcher ed(&in);
uint32_t wm = IN_CLOSE_WRITE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_UNMOUNT;
InotifyWatch stw(INCRON_SYS_TABLE_BASE, wm);
in.Add(stw);
InotifyWatch utw(INCRON_USER_TABLE_BASE, wm);
in.Add(utw);
EventDispatcher ed(g_cldPipe[0], &in, &stw, &utw);
try {
load_tables(&in, &ed);
load_tables(&ed);
} catch (InotifyException e) {
int err = e.GetErrorNumber();
syslog(LOG_CRIT, "%s: (%i) %s", e.GetMessage().c_str(), err, strerror(err));
203,68 → 375,72
return 1;
}
if (DAEMON)
daemon(0, 0);
prepare_pipe();
signal(SIGTERM, on_signal);
signal(SIGINT, on_signal);
signal(SIGCHLD, on_signal);
uint32_t wm = IN_CLOSE_WRITE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_UNMOUNT;
InotifyWatch watch(INCRON_TABLE_BASE, wm);
in.Add(watch);
syslog(LOG_NOTICE, "ready to process filesystem events");
InotifyEvent e;
struct pollfd pfd[2];
pfd[0].fd = in.GetDescriptor();
pfd[0].events = (short) POLLIN;
pfd[0].revents = (short) 0;
pfd[1].fd = g_cldPipe[0];
pfd[1].events = (short) POLLIN;
pfd[1].revents = (short) 0;
while (!g_fFinish) {
int res = poll(pfd, 2, -1);
int res = poll(ed.GetPollData(), ed.GetSize(), -1);
if (res > 0) {
if (pfd[1].revents & ((short) POLLIN)) {
char c;
while (read(pfd[1].fd, &c, 1) > 0) {}
if (ed.ProcessEvents())
UserTable::FinishDone();
}
else {
in.WaitForEvents(true);
}
}
else if (res < 0) {
if (errno != EINTR)
throw InotifyException("polling failed", errno, NULL);
}
/*
while (in.GetEvent(e)) {
if (e.GetWatch() == &watch) {
if (e.GetWatch() == &stw) {
if (e.IsType(IN_DELETE_SELF) || e.IsType(IN_UNMOUNT)) {
syslog(LOG_CRIT, "base directory destroyed, exitting");
g_fFinish = true;
}
else if (!e.GetName().empty()) {
SUT_MAP::iterator it = g_ut.find(e.GetName());
SUT_MAP::iterator it = g_ut.find(stw.GetPath() + e.GetName());
if (it != g_ut.end()) {
UserTable* pUt = (*it).second;
if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
syslog(LOG_INFO, "system table %s changed, reloading", e.GetName().c_str());
pUt->Dispose();
pUt->Load();
}
else if (e.IsType(IN_MOVED_FROM) || e.IsType(IN_DELETE)) {
syslog(LOG_INFO, "system table %s destroyed, removing", e.GetName().c_str());
delete pUt;
g_ut.erase(it);
}
}
else if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
syslog(LOG_INFO, "system table %s created, loading", e.GetName().c_str());
UserTable* pUt = new UserTable(&in, &ed, e.GetName(), true);
g_ut.insert(SUT_MAP::value_type(std::string(INCRON_SYS_TABLE_BASE) + e.GetName(), pUt));
pUt->Load();
}
}
}
else if (e.GetWatch() == &utw) {
if (e.IsType(IN_DELETE_SELF) || e.IsType(IN_UNMOUNT)) {
syslog(LOG_CRIT, "base directory destroyed, exitting");
g_fFinish = true;
}
else if (!e.GetName().empty()) {
SUT_MAP::iterator it = g_ut.find(e.GetWatch()->GetPath() + e.GetName());
if (it != g_ut.end()) {
UserTable* pUt = (*it).second;
if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
syslog(LOG_INFO, "table for user %s changed, reloading", e.GetName().c_str());
pUt->Dispose();
pUt->Load();
}
else if (e.IsType(IN_MOVED_FROM) || e.IsType(IN_DELETE)) {
syslog(LOG_INFO, "table for user %s destroyed, removing", e.GetName().c_str());
syslog(LOG_INFO, "table for user %s destroyed, removing", e.GetName().c_str());
delete pUt;
g_ut.erase(it);
}
272,8 → 448,8
else if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
if (check_user(e.GetName().c_str())) {
syslog(LOG_INFO, "table for user %s created, loading", e.GetName().c_str());
UserTable* pUt = new UserTable(&in, &ed, e.GetName());
g_ut.insert(SUT_MAP::value_type(e.GetName(), pUt));
UserTable* pUt = new UserTable(&in, &ed, e.GetName(), false);
g_ut.insert(SUT_MAP::value_type(std::string(INCRON_USER_TABLE_BASE) + e.GetName(), pUt));
pUt->Load();
}
}
284,6 → 460,8
}
}
*/
}
if (g_cldPipe[0] != -1)
/incron/trunk/inotify-cxx.cpp
546,6 → 546,36
IN_WRITE_END
}
 
void Inotify::SetCloseOnExec(bool fClOnEx) throw (InotifyException)
{
IN_WRITE_BEGIN
if (m_fd == -1) {
IN_WRITE_END_NOTHROW
throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this);
}
int res = fcntl(m_fd, F_GETFD);
if (res == -1) {
IN_WRITE_END_NOTHROW
throw InotifyException(IN_EXC_MSG("cannot get inotify flags"), errno, this);
}
if (fClOnEx) {
res |= FD_CLOEXEC;
}
else {
res &= ~FD_CLOEXEC;
}
if (fcntl(m_fd, F_SETFD, res) == -1) {
IN_WRITE_END_NOTHROW
throw InotifyException(IN_EXC_MSG("cannot set inotify flags"), errno, this);
}
IN_WRITE_END
}
 
uint32_t Inotify::GetCapability(InotifyCapability_t cap) throw (InotifyException)
{
FILE* f = fopen(GetCapabilityPath(cap).c_str(), "r");
/incron/trunk/incrontab.h
22,10 → 22,13
 
#include "strtok.h"
 
/// Incron table base directory
#define INCRON_TABLE_BASE "/var/spool/incron/"
/// Incron user table base directory
#define INCRON_USER_TABLE_BASE "/var/spool/incron/"
 
/// Incron system table base directory
#define INCRON_SYS_TABLE_BASE "/etc/incron.d/"
 
 
/// Incron table entry class.
class InCronTabEntry
{
211,6 → 214,15
* \attention No tests (existence, permission etc.) are done.
*/
static std::string GetUserTablePath(const std::string& rUser);
/// Composes a path to a system incron table file.
/**
* \param[in] rName table name (pseudouser)
* \return path to the table file
*
* \attention No tests (existence, permission etc.) are done.
*/
static std::string GetSystemTablePath(const std::string& rName);
 
protected:
std::deque<InCronTabEntry> m_tab; ///< incron table
/incron/trunk/inotify-cxx.h
743,14 → 743,32
* (acquired thru GetDescriptor()) in functions such as
* poll(), select() etc.
*
* Non-blocking mode is disabled by default.
*
* \param[in] fNonBlock enable/disable non-blocking mode
*
* \throw InotifyException thrown if setting mode failed
*
* \sa GetDescriptor()
* \sa GetDescriptor(), SetCloseOnExec()
*/
void SetNonBlock(bool fNonBlock) throw (InotifyException);
/// Enables/disables closing on exec.
/**
* Enable this if you want to close the descriptor when
* executing another program. Otherwise, the descriptor
* will be inherited.
*
* Closing on exec is disabled by default.
*
* \param[in] fClOnEx enable/disable closing on exec
*
* \throw InotifyException thrown if setting failed
*
* \sa GetDescriptor(), SetNonBlock()
*/
void SetCloseOnExec(bool fClOnEx) throw (InotifyException);
/// Acquires a particular inotify capability/limit.
/**
* \param[in] cap capability/limit identifier
/incron/trunk/strtok.cpp
5,7 → 5,7
*
* string tokenizer
*
* Copyright (C) 2006 Lukas Jelinek, <lukas@aiken.cz>
* Copyright (C) 2006, 2007 Lukas Jelinek, <lukas@aiken.cz>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of one of the following licenses:
97,8 → 97,9
 
void StringTokenizer::_GetNextTokenNoPrefix(std::string& rToken)
{
const char* s = m_str.c_str();
for (SIZE i=m_pos; i<m_len; i++) {
if (m_str[i] == m_cDelim) {
if (s[i] == m_cDelim) {
rToken = m_str.substr(m_pos, i - m_pos);
m_pos = i + 1;
return;
112,8 → 113,9
void StringTokenizer::_GetNextTokenWithPrefix(std::string& rToken)
{
int pref = 0;
const char* s = m_str.c_str();
for (SIZE i=m_pos; i<m_len; i++) {
if (m_str[i] == m_cDelim) {
if (s[i] == m_cDelim) {
if (pref == 0) {
rToken = m_str.substr(m_pos, i - m_pos);
m_pos = i + 1;
123,7 → 125,7
pref = 0;
}
}
else if (m_str[i] == m_cPrefix) {
else if (s[i] == m_cPrefix) {
if (pref == 1)
pref = 0;
else
/incron/trunk/usertable.cpp
25,15 → 25,18
#include "usertable.h"
 
#ifdef IN_DONT_FOLLOW
#define NO_FOLLOW(mask) InotifyEvent::IsType(mask, IN_DONT_FOLLOW)
#define DONT_FOLLOW(mask) InotifyEvent::IsType(mask, IN_DONT_FOLLOW)
#else // IN_DONT_FOLLOW
#define NO_FOLLOW(mask) (false)
#define DONT_FOLLOW(mask) (false)
#endif // IN_DONT_FOLLOW
 
 
PROC_LIST UserTable::s_procList;
 
extern volatile bool g_fFinish;
extern SUT_MAP g_ut;
 
 
void on_proc_done(InotifyWatch* pW)
{
pW->SetEnabled(true);
40,72 → 43,193
}
 
 
EventDispatcher::EventDispatcher(Inotify* pIn)
EventDispatcher::EventDispatcher(int iPipeFd, Inotify* pIn, InotifyWatch* pSys, InotifyWatch* pUser)
{
m_pIn = pIn;
m_iPipeFd = iPipeFd;
m_iMgmtFd = pIn->GetDescriptor();
m_pIn = pIn;
m_pSys = pSys;
m_pUser = pUser;
m_size = 0;
m_pPoll = NULL;
}
 
void EventDispatcher::DispatchEvent(InotifyEvent& rEvt)
EventDispatcher::~EventDispatcher()
{
if (m_pIn == NULL)
return;
if (m_pPoll != NULL)
delete[] m_pPoll;
}
 
bool EventDispatcher::ProcessEvents()
{
// consume pipe events if any (and report back)
bool pipe = (m_pPoll[0].revents & POLLIN);
if (pipe) {
char c;
while (read(m_pPoll[0].fd, &c, 1) > 0) {}
m_pPoll[0].revents = 0;
}
// process table management events if any
if (m_pPoll[1].revents & POLLIN) {
ProcessMgmtEvents();
m_pPoll[1].revents = 0;
}
InotifyWatch* pW = rEvt.GetWatch();
if (pW == NULL)
return;
InotifyEvent evt;
UserTable* pT = FindTable(pW);
if (pT == NULL)
return;
for (size_t i=2; i<m_size; i++) {
pT->OnEvent(rEvt);
// process events if occurred
if (m_pPoll[i].revents & POLLIN) {
FDUT_MAP::iterator it = m_maps.find(m_pPoll[i].fd);
if (it != m_maps.end()) {
Inotify* pIn = ((*it).second)->GetInotify();
pIn->WaitForEvents(true);
// process events for this object
while (pIn->GetEvent(evt)) {
((*it).second)->OnEvent(evt);
}
}
m_pPoll[i].revents = 0;
}
}
return pipe;
}
void EventDispatcher::Register(InotifyWatch* pWatch, UserTable* pTab)
void EventDispatcher::Register(UserTable* pTab)
{
if (pWatch != NULL && pTab != NULL)
m_maps.insert(IWUT_MAP::value_type(pWatch, pTab));
if (pTab != NULL) {
Inotify* pIn = pTab->GetInotify();
if (pIn != NULL) {
int fd = pIn->GetDescriptor();
if (fd != -1) {
m_maps.insert(FDUT_MAP::value_type(fd, pTab));
Rebuild();
}
}
}
}
void EventDispatcher::Unregister(InotifyWatch* pWatch)
void EventDispatcher::Unregister(UserTable* pTab)
{
IWUT_MAP::iterator it = m_maps.find(pWatch);
if (it == m_maps.end())
FDUT_MAP::iterator it = m_maps.find(pTab->GetInotify()->GetDescriptor());
if (it != m_maps.end()) {
m_maps.erase(it);
Rebuild();
}
}
 
void EventDispatcher::Rebuild()
{
// delete old data if exists
if (m_pPoll != NULL)
delete[] m_pPoll;
// allocate memory
m_size = m_maps.size() + 2;
m_pPoll = new struct pollfd[m_size];
void EventDispatcher::UnregisterAll(UserTable* pTab)
// add pipe descriptor
m_pPoll[0].fd = m_iPipeFd;
m_pPoll[0].events = POLLIN;
m_pPoll[0].revents = 0;
// add table management descriptor
m_pPoll[1].fd = m_iMgmtFd;
m_pPoll[1].events = POLLIN;
m_pPoll[1].revents = 0;
// add all inotify descriptors
FDUT_MAP::iterator it = m_maps.begin();
for (size_t i=2; i<m_size; i++, it++) {
m_pPoll[i].fd = (*it).first;
m_pPoll[i].events = POLLIN;
m_pPoll[i].revents = 0;
}
}
 
void EventDispatcher::ProcessMgmtEvents()
{
IWUT_MAP::iterator it = m_maps.begin();
while (it != m_maps.end()) {
if ((*it).second == pTab) {
IWUT_MAP::iterator it2 = it;
it++;
m_maps.erase(it2);
m_pIn->WaitForEvents(true);
InotifyEvent e;
while (m_pIn->GetEvent(e)) {
if (e.GetWatch() == m_pSys) {
if (e.IsType(IN_DELETE_SELF) || e.IsType(IN_UNMOUNT)) {
syslog(LOG_CRIT, "base directory destroyed, exitting");
g_fFinish = true;
}
else if (!e.GetName().empty()) {
SUT_MAP::iterator it = g_ut.find(m_pSys->GetPath() + e.GetName());
if (it != g_ut.end()) {
UserTable* pUt = (*it).second;
if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
syslog(LOG_INFO, "system table %s changed, reloading", e.GetName().c_str());
pUt->Dispose();
pUt->Load();
}
else if (e.IsType(IN_MOVED_FROM) || e.IsType(IN_DELETE)) {
syslog(LOG_INFO, "system table %s destroyed, removing", e.GetName().c_str());
delete pUt;
g_ut.erase(it);
}
}
else if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
syslog(LOG_INFO, "system table %s created, loading", e.GetName().c_str());
UserTable* pUt = new UserTable(this, e.GetName(), true);
g_ut.insert(SUT_MAP::value_type(std::string(INCRON_SYS_TABLE_BASE) + e.GetName(), pUt));
pUt->Load();
}
}
}
else {
it++;
else if (e.GetWatch() == m_pUser) {
if (e.IsType(IN_DELETE_SELF) || e.IsType(IN_UNMOUNT)) {
syslog(LOG_CRIT, "base directory destroyed, exitting");
g_fFinish = true;
}
else if (!e.GetName().empty()) {
SUT_MAP::iterator it = g_ut.find(m_pUser->GetPath() + e.GetName());
if (it != g_ut.end()) {
UserTable* pUt = (*it).second;
if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
syslog(LOG_INFO, "table for user %s changed, reloading", e.GetName().c_str());
pUt->Dispose();
pUt->Load();
}
else if (e.IsType(IN_MOVED_FROM) || e.IsType(IN_DELETE)) {
syslog(LOG_INFO, "table for user %s destroyed, removing", e.GetName().c_str());
delete pUt;
g_ut.erase(it);
}
}
else if (e.IsType(IN_CLOSE_WRITE) || e.IsType(IN_MOVED_TO)) {
if (UserTable::CheckUser(e.GetName().c_str())) {
syslog(LOG_INFO, "table for user %s created, loading", e.GetName().c_str());
UserTable* pUt = new UserTable(this, e.GetName(), false);
g_ut.insert(SUT_MAP::value_type(std::string(INCRON_USER_TABLE_BASE) + e.GetName(), pUt));
pUt->Load();
}
}
}
}
}
}
 
UserTable* EventDispatcher::FindTable(InotifyWatch* pW)
{
IWUT_MAP::iterator it = m_maps.find(pW);
if (it == m_maps.end())
return NULL;
return (*it).second;
}
 
 
 
 
UserTable::UserTable(Inotify* pIn, EventDispatcher* pEd, const std::string& rUser)
: m_user(rUser)
UserTable::UserTable(EventDispatcher* pEd, const std::string& rUser, bool fSysTable)
: m_user(rUser),
m_fSysTable(fSysTable)
{
m_pIn = pIn;
m_pEd = pEd;
m_in.SetNonBlock(true);
m_in.SetCloseOnExec(true);
}
 
UserTable::~UserTable()
115,7 → 239,9
void UserTable::Load()
{
m_tab.Load(InCronTab::GetUserTablePath(m_user));
m_tab.Load(m_fSysTable
? InCronTab::GetSystemTablePath(m_user)
: InCronTab::GetUserTablePath(m_user));
int cnt = m_tab.GetCount();
for (int i=0; i<cnt; i++) {
123,27 → 249,32
InotifyWatch* pW = new InotifyWatch(rE.GetPath(), rE.GetMask());
// warning only - permissions may change later
if (!MayAccess(rE.GetPath(), NO_FOLLOW(rE.GetMask())))
if (!(m_fSysTable || MayAccess(rE.GetPath(), DONT_FOLLOW(rE.GetMask()))))
syslog(LOG_WARNING, "access denied on %s - events will be discarded silently", rE.GetPath().c_str());
try {
m_pIn->Add(pW);
m_pEd->Register(pW, this);
m_in.Add(pW);
m_map.insert(IWCE_MAP::value_type(pW, &rE));
} catch (InotifyException e) {
syslog(LOG_ERR, "cannot create watch for user %s", m_user.c_str());
if (m_fSysTable)
syslog(LOG_ERR, "cannot create watch for system table %s: (%i) %s", m_user.c_str(), e.GetErrorNumber(), strerror(e.GetErrorNumber()));
else
syslog(LOG_ERR, "cannot create watch for user %s: (%i) %s", m_user.c_str(), e.GetErrorNumber(), strerror(e.GetErrorNumber()));
delete pW;
}
}
m_pEd->Register(this);
}
 
void UserTable::Dispose()
{
m_pEd->Unregister(this);
IWCE_MAP::iterator it = m_map.begin();
while (it != m_map.end()) {
InotifyWatch* pW = (*it).first;
m_pEd->Unregister(pW);
m_pIn->Remove(pW);
m_in.Remove(pW);
delete pW;
it++;
}
161,7 → 292,7
return;
// discard event if user has no access rights to watch path
if (!MayAccess(pW->GetPath(), NO_FOLLOW(rEvt.GetMask())))
if (!(m_fSysTable || MayAccess(pW->GetPath(), DONT_FOLLOW(rEvt.GetMask()))))
return;
std::string cmd;
218,7 → 349,10
return;
}
syslog(LOG_INFO, "(%s) CMD (%s)", m_user.c_str(), cmd.c_str());
if (m_fSysTable)
syslog(LOG_INFO, "(system::%s) CMD (%s)", m_user.c_str(), cmd.c_str());
else
syslog(LOG_INFO, "(%s) CMD (%s)", m_user.c_str(), cmd.c_str());
if (pE->IsNoLoop())
pW->SetEnabled(false);
227,15 → 361,26
pd.pid = fork();
if (pd.pid == 0) {
struct passwd* pwd = getpwnam(m_user.c_str());
if ( pwd == NULL // user not found
|| setgid(pwd->pw_gid) != 0 // setting GID failed
|| setuid(pwd->pw_uid) != 0 // setting UID failed
|| execvp(argv[0], argv) != 0) // exec failed
{
syslog(LOG_ERR, "cannot exec process: %s", strerror(errno));
_exit(1);
// for system table
if (m_fSysTable) {
if (execvp(argv[0], argv) != 0) // exec failed
{
syslog(LOG_ERR, "cannot exec process: %s", strerror(errno));
_exit(1);
}
}
else {
// for user table
struct passwd* pwd = getpwnam(m_user.c_str());
if ( pwd == NULL // user not found
|| setgid(pwd->pw_gid) != 0 // setting GID failed
|| setuid(pwd->pw_uid) != 0 // setting UID failed
|| execvp(argv[0], argv) != 0) // exec failed
{
syslog(LOG_ERR, "cannot exec process: %s", strerror(errno));
_exit(1);
}
}
}
else if (pd.pid > 0) {
if (pE->IsNoLoop()) {
340,6 → 485,10
// retrieve user data
struct passwd* pwd = getpwnam(m_user.c_str());
// root may always access
if (pwd->pw_uid == 0)
return true;
// file accesible to group
if (st.st_mode & S_IRWXG) {
/incron/trunk/usertable.h
18,6 → 18,7
 
#include <map>
#include <deque>
#include <sys/poll.h>
 
#include "inotify-cxx.h"
#include "incrontab.h"
25,6 → 26,9
 
class UserTable;
 
/// User name to user table mapping definition
typedef std::map<std::string, UserTable*> SUT_MAP;
 
/// Callback for calling after a process finishes.
typedef void (*proc_done_cb)(InotifyWatch*);
 
36,8 → 40,8
InotifyWatch* pWatch; ///< related watch
} ProcData_t;
 
/// Watch-to-usertable mapping
typedef std::map<InotifyWatch*, UserTable*> IWUT_MAP;
/// fd-to-usertable mapping
typedef std::map<int, UserTable*> FDUT_MAP;
 
/// Watch-to-tableentry mapping
typedef std::map<InotifyWatch*, InCronTabEntry*> IWCE_MAP;
47,7 → 51,7
 
/// Event dispatcher class.
/**
* This class distributes inotify events to appropriate user tables.
* This class processes events and distributes them as needed.
*/
class EventDispatcher
{
54,48 → 58,68
public:
/// Constructor.
/**
* \param[in] pIn inotify object
* \param[in] iPipeFd pipe descriptor
* \param[in] pIn inotify object for table management
* \param[in] pSys watch for system tables
* \param[in] pUser watch for user tables
*/
EventDispatcher(Inotify* pIn);
EventDispatcher(int iPipeFd, Inotify* pIn, InotifyWatch* pSys, InotifyWatch* pUser);
/// Destructor.
~EventDispatcher() {}
~EventDispatcher();
 
/// Dispatches an event.
/// Processes events.
/**
* \param[in] rEvt inotify event
* \return pipe event occurred yes/no
*/
void DispatchEvent(InotifyEvent& rEvt);
bool ProcessEvents();
/// Registers a watch for an user table.
/// Registers an user table.
/**
* \param[in] pWatch inotify watch
* \param[in] pTab user table
*/
void Register(InotifyWatch* pWatch, UserTable* pTab);
void Register(UserTable* pTab);
/// Unregisters a watch.
/// Unregisters an user table.
/**
* \param[in] pWatch inotify watch
* \param[in] pTab user table
*/
void Unregister(InotifyWatch* pWatch);
void Unregister(UserTable* pTab);
/// Unregisters all watches for an user table.
/// Returns the poll data size.
/**
* \param[in] pTab user table
* \return poll data size
*/
void UnregisterAll(UserTable* pTab);
inline size_t GetSize() const
{
return m_size;
}
/// Returns the poll data.
/**
* \return poll data
*/
inline struct pollfd* GetPollData()
{
return m_pPoll;
}
private:
Inotify* m_pIn; ///< inotify object
IWUT_MAP m_maps; ///< watch-to-usertable mapping
int m_iPipeFd; ///< pipe file descriptor
int m_iMgmtFd; ///< table management file descriptor
Inotify* m_pIn; ///< table management inotify object
InotifyWatch* m_pSys; ///< watch for system tables
InotifyWatch* m_pUser; ///< watch for user tables
FDUT_MAP m_maps; ///< watch-to-usertable mapping
size_t m_size; ///< poll data size
struct pollfd* m_pPoll; ///< poll data array
/// Finds an user table for a watch.
/**
* \param[in] pW inotify watch
* \return pointer to the appropriate watch; NULL if no such watch exists
*/
UserTable* FindTable(InotifyWatch* pW);
/// Rebuilds the poll array data.
void Rebuild();
/// Processes events on the table management inotify object.
void ProcessMgmtEvents();
};
 
 
110,11 → 134,11
public:
/// Constructor.
/**
* \param[in] pIn inotify object
* \param[in] pEd event dispatcher
* \param[in] rUser user name
* \param[in] fSysTable system table yes/no
*/
UserTable(Inotify* pIn, EventDispatcher* pEd, const std::string& rUser);
UserTable(EventDispatcher* pEd, const std::string& rUser, bool fSysTable);
/// Destructor.
virtual ~UserTable();
157,10 → 181,46
*/
bool MayAccess(const std::string& rPath, bool fNoFollow) const;
/// Checks whether it is a system table.
/**
* \return true = system table, false = user table
*/
bool IsSystem() const;
/// Returns the related inotify object.
/**
* \return related inotify object
*/
Inotify* GetInotify()
{
return &m_in;
}
/// Checks whether an user exists and has permission to use incron.
/**
* It searches for the given user name in the user database.
* If it failes it returns 'false'. Otherwise it checks
* permission files for this user (see InCronTab::CheckUser()).
*
* \param[in] user user name
* \return true = user has permission to use incron, false = otherwise
*
* \sa InCronTab::CheckUser()
*/
inline static bool CheckUser(const char* user)
{
struct passwd* pw = getpwnam(user);
if (pw == NULL)
return false;
return InCronTab::CheckUser(user);
}
private:
Inotify* m_pIn; ///< inotify object
Inotify m_in; ///< inotify object
EventDispatcher* m_pEd; ///< event dispatcher
std::string m_user; ///< user name
bool m_fSysTable; ///< system table yes/no
InCronTab m_tab; ///< incron table
IWCE_MAP m_map; ///< watch-to-entry mapping
 
/incron/trunk/Makefile
1,6 → 1,7
 
PREFIX = /usr/local
DATADIR = /var/spool/incron
USERDATADIR = /var/spool/incron
SYSDATADIR = /etc/incron.d
MANPATH = /usr/share/man
RELEASE = incron-`cat VERSION`
RELEASEDIR = /tmp/$(RELEASE)
46,7 → 47,8
[ -d $(PREFIX) ]
$(INSTALL) -m 04755 -o $(USER) incrontab $(PREFIX)/bin/
$(INSTALL) -m 0755 incrond $(PREFIX)/sbin/
$(INSTALL) -m 0755 -o $(USER) -d $(DATADIR)
$(INSTALL) -m 0755 -o $(USER) -d $(USERDATADIR)
$(INSTALL) -m 0755 -o $(USER) -d $(SYSDATADIR)
 
install-man: incrontab.1 incrontab.5 incrond.8
$(INSTALL) -m 0755 -d $(MANPATH)/man1
/incron/trunk/incrontab.1
1,10 → 1,10
.TH "incrontab" "1" "0.3.5" "Lukas Jelinek" "incron documentation"
.TH "incrontab" "1" "0.4.0" "Lukas Jelinek" "incron documentation"
.SH "NAME"
incrontab \- table manipulator for inotify cron (incron)
.SH "SYNOPSIS"
\fBincrontab\fR [\fB\-u\fR \fIuser\fR] \fIfile\fR
 
\fBincrontab\fR [\fB\-u\fR \fIuser\fR] [\fB\-l\fR | \fB\-r\fR | \fB\-e\fR]
\fBincrontab\fR [\fB\-u\fR \fIuser\fR] [\fB\-l\fR | \fB\-r\fR | \fB\-e\fR | \fB\-t\fR]
.SH "DESCRIPTION"
incrontab is a table manipulator for the inotify cron (incron) system. It creates, removes, modifies and lists user tables (\fIincrontab\fR(5)).
 
25,6 → 25,8
\fB\-r\fR (or \fB\-\-remove\fR) option causes the current table (if any) is permanently remove without any warning or confirmation. Use with caution!
 
\fB\-e\fR (or \fB\-\-edit\fR) option causes executing the editor specified by the EDITOR environment variable (if not defined the hard\-coded editor is executed instead). You can edit your incron table now. If the table is changed it stores the modified version. It's not recommended to use graphical editors (such as gVim, KEdit etc.) due to possible problems with connecting to the X server.
 
\fB\-t\fR (or \fB\-\-types\fR) option causes the list of supported event types (delimited by commas) is printed to the standard output. This feature is intended for front\-end applications to find out which event types was compiled in.
.SH "SEE ALSO"
incrond(8), incrontab(5)
.SH "AUTHOR"
/incron/trunk/incron.h
27,7 → 27,7
#define INCRON_TAB_NAME "incrontab"
 
/// Application version (release)
#define INCRON_VERSION "0.3.5"
#define INCRON_VERSION "0.4.0"
 
/// Address for sending bugs
#define INCRON_BUG_ADDRESS "<bugs@aiken.cz>"