Subversion Repositories public

Compare Revisions

Ignore whitespace Rev 67 → Rev 69

/incron/trunk/incrontab.5
1,10 → 1,10
.TH "incrontab" "5" "0.4.0" "Lukas Jelinek" "incron documentation"
.TH "incrontab" "5" "0.5.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". 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.
System tables are (by default) 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.
 
/incron/trunk/ict-main.cpp
22,117 → 22,73
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/inotify.h>
#include <fcntl.h>
 
#include "inotify-cxx.h"
#include "appargs.h"
 
#include "incron.h"
#include "incrontab.h"
#include "incroncfg.h"
 
// Alternative editor
 
/// Alternative editor
#define INCRON_ALT_EDITOR "/etc/alternatives/editor"
 
// #define INCRON_DEFAULT_EDITOR "nano" // for vim haters like me ;-)
/// Default (hard-wired) editor
#define INCRON_DEFAULT_EDITOR "vim"
 
/// incrontab version string
#define INCRONTAB_VERSION INCRONTAB_NAME " " INCRON_VERSION
 
const char* argp_program_version = INCRON_TAB_NAME " " INCRON_VERSION;
const char* argp_program_bug_address = INCRON_BUG_ADDRESS;
/// incrontab description string
#define INCRONTAB_DESCRIPTION "incrontab - inotify cron table manipulator\n" \
"(c) Lukas Jelinek, 2006, 2007"
 
static char doc[] = "incrontab - incron table manipulator\n(c) Lukas Jelinek, 2006, 2007";
/// incrontab help string
#define INCRONTAB_HELP INCRONTAB_DESCRIPTION "\n\n" \
"usage: incrontab [<options>] <operation>\n" \
" incrontab [<options>] <FILE-TO-IMPORT>\n\n" \
"<operation> may be one of the following:\n" \
" -?, --about gives short information about program\n" \
" -h, --help prints this help text\n" \
" -l, --list lists user table\n" \
" -r, --remove removes user table\n" \
" -e, --edit provides editting user table\n" \
" -t, --types list supported event types\n" \
" -d, --reload request incrond to reload user table\n" \
" -V, --version prints program version\n\n" \
"\n" \
"These options may be used:\n" \
" -u <USER>, --user=<USER> overrides current user (requires root privileges)\n" \
" -f <FILE>, --config=<FILE> overrides default configuration file (requires root privileges)\n\n" \
"For reporting bugs please use http:://bts.aiken.cz\n"
 
static char args_doc[] = "FILE";
 
static struct argp_option options[] = {
{"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 }
};
 
/// incrontab operations
typedef enum
{
OPER_NONE, /// nothing
OPER_LIST, /// list table
OPER_REMOVE, /// remove table
OPER_EDIT, /// edit table
OPER_TYPES /// list event types
} InCronTab_Operation_t;
 
/// incrontab arguments
struct arguments
{
char *user; /// user name
int oper; /// operation code
char *file; /// file to import
};
 
/// Parses the program options (arguments).
/**
* \param[in] key argument key (name)
* \param[in] arg argument value
* \param[out] state options setting
* \return 0 on success, ARGP_ERR_UNKNOWN on unknown argument(s)
*/
static error_t parse_opt(int key, char *arg, struct argp_state *state)
{
struct arguments* arguments = (struct arguments*) state->input;
switch (key) {
case 'l':
arguments->oper = OPER_LIST;
break;
case 'r':
arguments->oper = OPER_REMOVE;
break;
case 'e':
arguments->oper = OPER_EDIT;
break;
case 't':
arguments->oper = OPER_TYPES;
break;
case 'u':
arguments->user = arg;
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 1)
argp_usage(state);
arguments->file = arg;
break;
case ARGP_KEY_END:
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
 
/// Program arguments
static struct argp argp = { options, parse_opt, args_doc, doc };
 
 
 
/// Copies a file to an user table.
/**
* \param[in] path path to file
* \param[in] user user name
* \param[in] rPath path to file
* \param[in] rUser user name
* \return true = success, false = failure
*/
bool copy_from_file(const char* path, const char* user)
bool copy_from_file(const std::string& rPath, const std::string& rUser)
{
InCronTab tab;
std::string s(path);
fprintf(stderr, "copying table from file '%s'\n", rPath.c_str());
IncronTab tab;
std::string s(rPath);
if (s == "-")
s = "/dev/stdin";
if (!tab.Load(s)) {
fprintf(stderr, "cannot load table from file: %s\n", path);
fprintf(stderr, "cannot load table from file '%s'\n", rPath.c_str());
return false;
}
std::string out(InCronTab::GetUserTablePath(user));
std::string out(IncronTab::GetUserTablePath(rUser));
if (!tab.Save(out)) {
fprintf(stderr, "cannot create table for user: %s\n", user);
fprintf(stderr, "cannot create table for user '%s'\n", rUser.c_str());
return false;
}
141,15 → 97,17
 
/// Removes an user table.
/**
* \param[in] user user name
* \param[in] rUser user name
* \return true = success, false = failure
*/
bool remove_table(const char* user)
bool remove_table(const std::string& rUser)
{
std::string tp(InCronTab::GetUserTablePath(user));
fprintf(stderr, "removing table for user '%s'\n", rUser.c_str());
std::string tp(IncronTab::GetUserTablePath(rUser));
if (unlink(tp.c_str()) != 0 && errno != ENOENT) {
fprintf(stderr, "cannot remove table for user: %s\n", user);
fprintf(stderr, "cannot remove table for user '%s': %s\n", rUser.c_str(), strerror(errno));
return false;
}
158,27 → 116,24
 
/// Lists an user table.
/**
* \param[in] user user name
* \param[in] rUser user name
* \return true = success, false = failure
*/
bool list_table(const char* user)
bool list_table(const std::string& rUser)
{
std::string tp(InCronTab::GetUserTablePath(user));
std::string tp(IncronTab::GetUserTablePath(rUser));
if (euidaccess(tp.c_str(), R_OK) != 0) {
FILE* f = fopen(tp.c_str(), "r");
if (f == NULL) {
if (errno == ENOENT) {
fprintf(stderr, "no table for %s\n", user);
fprintf(stderr, "no table for %s\n", rUser.c_str());
return true;
}
else {
fprintf(stderr, "cannot read table for %s: %s\n", user, strerror(errno));
fprintf(stderr, "cannot read table for '%s': %s\n", rUser.c_str(), strerror(errno));
return false;
}
}
FILE* f = fopen(tp.c_str(), "r");
if (f == NULL)
return false;
char s[1024];
while (fgets(s, 1024, f) != NULL) {
192,7 → 147,7
 
/// Allows to edit an user table.
/**
* \param[in] user user name
* \param[in] rUser user name
* \return true = success, false = failure
*
* \attention This function is very complex and may contain
199,13 → 154,13
* various bugs including security ones. Please keep
* it in mind..
*/
bool edit_table(const char* user)
bool edit_table(const std::string& rUser)
{
std::string tp(InCronTab::GetUserTablePath(user));
std::string tp(IncronTab::GetUserTablePath(rUser));
struct passwd* ppwd = getpwnam(user);
struct passwd* ppwd = getpwnam(rUser.c_str());
if (ppwd == NULL) {
fprintf(stderr, "cannot find user %s: %s\n", user, strerror(errno));
fprintf(stderr, "cannot find user '%s': %s\n", rUser.c_str(), strerror(errno));
return false;
}
219,7 → 174,7
uid_t ig = getegid();
 
if (setegid(gid) != 0 || seteuid(uid) != 0) {
fprintf(stderr, "cannot change effective UID/GID for user %s: %s\n", user, strerror(errno));
fprintf(stderr, "cannot change effective UID/GID for user '%s': %s\n", rUser.c_str(), strerror(errno));
return false;
}
234,6 → 189,7
FILE* in = NULL;
time_t mt = (time_t) 0;
const char* e = NULL;
std::string ed;
if (setegid(ig) != 0 || seteuid(iu) != 0) {
fprintf(stderr, "cannot change effective UID/GID: %s\n", strerror(errno));
253,13 → 209,13
if (errno == ENOENT) {
in = fopen("/dev/null", "r");
if (in == NULL) {
fprintf(stderr, "cannot get empty table for %s: %s\n", user, strerror(errno));
fprintf(stderr, "cannot get empty table for '%s': %s\n", rUser.c_str(), strerror(errno));
fclose(out);
goto end;
}
}
else {
fprintf(stderr, "cannot read old table for %s: %s\n", user, strerror(errno));
fprintf(stderr, "cannot read old table for '%s': %s\n", rUser.c_str(), strerror(errno));
fclose(out);
goto end;
}
283,16 → 239,27
// 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
// 3. Try to get from configuration
// 4. Check presence of /etc/alternatives/editor
// 5. Use hard-wired editor
e = getenv("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;
if (!IncronCfg::GetValue("editor", ed))
throw InotifyException("configuration is corrupted", EINVAL);
if (!ed.empty()) {
e = ed.c_str();
}
else {
if (access(INCRON_ALT_EDITOR, X_OK) == 0)
e = INCRON_ALT_EDITOR;
else
e = INCRON_DEFAULT_EDITOR;
}
}
}
301,7 → 268,7
pid_t pid = fork();
if (pid == 0) {
if (setgid(gid) != 0 || setuid(uid) != 0) {
fprintf(stderr, "cannot set user %s: %s\n", user, strerror(errno));
fprintf(stderr, "cannot set user '%s': %s\n", rUser.c_str(), strerror(errno));
goto end;
}
337,7 → 304,7
}
{
InCronTab ict;
IncronTab ict;
if (ict.Load(s) && ict.Save(tp)) {
if (chmod(tp.c_str(), S_IRUSR | S_IWUSR) != 0) {
fprintf(stderr, "cannot change mode of temporary file: %s\n", strerror(errno));
383,36 → 350,103
printf("\n");
}
 
/// Reloads an user table.
/**
* \param[in] rUser user name
* \return true = success, false = otherwise
*/
bool reload_table(const std::string& rUser)
{
fprintf(stderr, "requesting table reload for user '%s'...\n", rUser.c_str());
std::string tp(IncronTab::GetUserTablePath(rUser));
int fd = open(tp.c_str(), O_WRONLY | O_APPEND);
if (fd == -1) {
if (errno == ENOENT) {
fprintf(stderr, "no table for '%s'\n", rUser.c_str());
return true;
}
else {
fprintf(stderr, "cannot access table for '%s': %s\n", rUser.c_str(), strerror(errno));
return false;
}
}
close(fd);
fprintf(stderr, "request done\n");
return true;
}
 
int main(int argc, char** argv)
{
struct arguments arguments;
AppArgs::Init();
 
if (!( AppArgs::AddOption("about", '?', AAT_NO_VALUE, false)
&& AppArgs::AddOption("help", 'h', AAT_NO_VALUE, false)
&& AppArgs::AddOption("list", 'l', AAT_NO_VALUE, false)
&& AppArgs::AddOption("remove", 'r', AAT_NO_VALUE, false)
&& AppArgs::AddOption("edit", 'e', AAT_NO_VALUE, false)
&& AppArgs::AddOption("types", 't', AAT_NO_VALUE, false)
&& AppArgs::AddOption("reload", 'd', AAT_NO_VALUE, false)
&& AppArgs::AddOption("user", 'u', AAT_MANDATORY_VALUE, false)
&& AppArgs::AddOption("config", 'f', AAT_MANDATORY_VALUE, false))
&& AppArgs::AddOption("version", 'V', AAT_NO_VALUE, false))
{
fprintf(stderr, "error while initializing application");
return 1;
}
arguments.user = NULL;
arguments.oper = OPER_NONE;
arguments.file = NULL;
AppArgs::Parse(argc, argv);
argp_parse (&argp, argc, argv, 0, 0, &arguments);
if (AppArgs::ExistsOption("help")) {
fprintf(stderr, "%s\n", INCRONTAB_HELP);
return 0;
}
if (arguments.file != NULL && arguments.oper != OPER_NONE) {
fprintf(stderr, "invalid arguments - specify source file or operation\n");
if (AppArgs::ExistsOption("about")) {
fprintf(stderr, "%s\n", INCRONTAB_DESCRIPTION);
return 0;
}
if (AppArgs::ExistsOption("version")) {
fprintf(stderr, "%s\n", INCRONTAB_VERSION);
return 0;
}
bool oper = AppArgs::ExistsOption("list")
|| AppArgs::ExistsOption("remove")
|| AppArgs::ExistsOption("edit")
|| AppArgs::ExistsOption("types")
|| AppArgs::ExistsOption("reload");
 
size_t vals = AppArgs::GetValueCount();
if (!oper && vals == 0) {
fprintf(stderr, "invalid arguments - specify operation or source file\n");
return 1;
}
if (arguments.file == NULL && arguments.oper == OPER_NONE) {
fprintf(stderr, "invalid arguments - specify source file or operation\n");
if (oper && vals > 0) {
fprintf(stderr, "invalid arguments - operation and source file cannot be combined\n");
return 1;
}
uid_t uid = getuid();
if (uid != 0 && arguments.user != NULL) {
fprintf(stderr, "cannot access table for user %s: permission denied\n", arguments.user);
std::string user;
bool chuser = AppArgs::GetOption("user", user);
if (uid != 0 && chuser) {
fprintf(stderr, "cannot override user to '%s': insufficient privileges\n", user.c_str());
return 1;
}
struct passwd pwd;
if (arguments.user == NULL) {
if (!chuser) {
struct passwd* ppwd = getpwuid(uid);
if (ppwd == NULL) {
fprintf(stderr, "cannot determine current user\n");
419,44 → 453,75
return 1;
}
memcpy(&pwd, ppwd, sizeof(pwd));
arguments.user = pwd.pw_name;
user = pwd.pw_name;
}
else if (getpwnam(arguments.user) == NULL) {
fprintf(stderr, "user %s not found\n", arguments.user);
else if (getpwnam(user.c_str()) == NULL) {
fprintf(stderr, "user '%s' not found\n", user.c_str());
return 1;
}
if (!InCronTab::CheckUser(arguments.user)) {
fprintf(stderr, "user %s is not allowed to use incron\n", arguments.user);
return 1;
}
try {
switch (arguments.oper) {
case OPER_NONE:
fprintf(stderr, "copying table from file: %s\n", arguments.file);
if (!copy_from_file(arguments.file, arguments.user))
IncronCfg::Init();
std::string cfg(INCRON_CONFIG);
if (AppArgs::GetOption("config", cfg)) {
if (uid != 0) {
fprintf(stderr, "insufficient privileges to use custom configuration (will use default)\n");
}
else if (euidaccess(cfg.c_str(), R_OK) != 0) {
perror("cannot read configuration file (will use default)");
}
}
IncronCfg::Load(cfg);
if (!IncronTab::CheckUser(user)) {
fprintf(stderr, "user '%s' is not allowed to use incron\n", user.c_str());
return 1;
}
if (!oper) {
std::string file;
if (!AppArgs::GetValue(0, file)
|| !copy_from_file(file, user))
{
return 1;
break;
case OPER_LIST:
if (!list_table(arguments.user))
}
}
else {
if (AppArgs::ExistsOption("list")) {
if (!list_table(user))
return 1;
}
else if (AppArgs::ExistsOption("remove")) {
if (!remove_table(user))
return 1;
}
else if (AppArgs::ExistsOption("edit")) {
if (!edit_table(user))
return 1;
}
else if (AppArgs::ExistsOption("types")) {
list_types();
}
else if (AppArgs::ExistsOption("reload")) {
if (!reload_table(user))
return 1;
}
else {
fprintf(stderr, "invalid usage\n");
return 1;
break;
case OPER_REMOVE:
fprintf(stderr, "removing table for user %s\n", arguments.user);
if (!remove_table(arguments.user))
return 1;
break;
case OPER_EDIT:
if (!edit_table(arguments.user))
return 1;
break;
case OPER_TYPES:
list_types();
break;
default:
fprintf(stderr, "invalid usage\n");
return 1;
}
}
return 0;
} catch (InotifyException e) {
fprintf(stderr, "*** unhandled exception occurred ***\n");
fprintf(stderr, "%s\n", e.GetMessage().c_str());
fprintf(stderr, "error: (%i) %s\n", e.GetErrorNumber(), strerror(e.GetErrorNumber()));
return 1;
}
return 0;
}
/incron/trunk/appargs.h
0,0 → 1,234
 
/// application arguments processor header
/**
* \file appargs.h
*
* application arguments processor
*
* Copyright (C) 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:
*
* \li 1. X11-style license (see LICENSE-X11)
* \li 2. GNU Lesser General Public License, version 2.1 (see LICENSE-LGPL)
* \li 3. GNU General Public License, version 2 (see LICENSE-GPL)
*
* If you want to help with choosing the best license for you,
* please visit http://www.gnu.org/licenses/license-list.html.
*
*/
 
#ifndef APPARGS_H_
#define APPARGS_H_
 
#include <string>
#include <map>
#include <deque>
 
 
 
/// Option argument type
typedef enum
{
AAT_NO_VALUE, ///< no value needed
AAT_OPTIONAL_VALUE, ///< optional value
AAT_MANDATORY_VALUE ///< mandatory value
} AppArgType_t;
 
 
#define APPARGS_NOLIMIT 0x7fffffff ///< value count has no limit
 
/// Argument option type
typedef struct
{
AppArgType_t type; ///< argument type
bool mand; ///< mandatory yes/no
bool found; ///< found in argument vector
std::string val; ///< value
bool hasVal; ///< value is set
} AppArgOption_t;
 
 
/// Mapping from long option name to option data
typedef std::map<std::string, AppArgOption_t*> AA_LONG_MAP;
 
/// Mapping from short option name to option data
typedef std::map<char, AppArgOption_t*> AA_SHORT_MAP;
 
/// Value list type
typedef std::deque<std::string> AA_VAL_LIST;
 
 
/// Application arguments
/**
* This class is set-up for processing command line arguments.
* Then it parses these arguments and builds data which
* can be queried later.
*
* There are two categories of arguments:
* \li options (a.k.a. switches)
* \li values
*
* Options represent changeable parameters of the application.
* Values are a kind of input data.
*
* Each option has one of the following types:
* \li no value (two-state logic, e.g. running on foreground/background)
* \li optional value (e.g. for logging: another file than default can be specified)
* \li mandatory value (e.g. custom configuration file)
*
* Each option always have two forms - long one (introcuded by
* two hyphens, e.g. --edit) and short one (introduced by one
* hyphen, e.g. -e). These forms are functionally equivalent.
*
* Unknown options are silently ignored.
*/
class AppArgs
{
public:
/// Initializes the processor.
/**
* \param[in] valMinCnt minimum count of values
* \param[in] valMaxCnt maximum number of values (no effect if lower than valMinCnt)
*/
static void Init(size_t valMinCnt = 0, size_t valMaxCnt = APPARGS_NOLIMIT);
 
/// Releases resources allocated by the processor.
/**
* This method should be called if the argument values are
* no longer needed.
*/
static void Destroy();
 
/// Parses arguments and builds the appropriate structure.
/**
* \param[in] argc argument count
* \param[in] argv argument vector
*
* \attention All errors are silently ignored.
*/
static void Parse(int argc, const char* const* argv);
/// Checks whether the arguments have valid form.
/**
* Arguments are valid if:
* \li all mandatory options are present
* \li all options with mandatory values have their values
* \li value count is between its minimum and maximum
* \li there are no unknown options (if unknown options are not accepted)
*
* \return true = arguments valid, false = otherwise
*/
static bool IsValid();
/// Checks whether an option exists.
/**
* \param[in] rArg long option name
* \return true = option exists, false = otherwise
*/
static bool ExistsOption(const std::string& rArg);
/// Extracts an option value.
/**
* \param[in] rArg long option name
* \param[out] rVal option value
* \return true = value extracted, false = option not found or has no value
*/
static bool GetOption(const std::string& rArg, std::string& rVal);
 
/// Adds an option.
/**
* This method is intended to be called between initilization
* and parsing. It adds an option which may (or must) occur
* inside the argument vector.
*
* \param[in] rName long option name
* \param[in] cShort short (one-character) option name
* \param[in] type argument type
* \param[in] fMandatory option is mandatory yes/no
* \return true = success, false = failure (e.g. option already exists)
*/
static bool AddOption(const std::string& rName, char cShort, AppArgType_t type, bool fMandatory);
/// Returns the count of values.
/**
* \return count of values
*/
static size_t GetValueCount();
/// Extracts a value.
/**
* \param[in] index value index
* \param[out] rVal extracted value
* \return true = value extracted, false = otherwise
*/
static bool GetValue(size_t index, std::string& rVal);
/// Dumps information about options and value to STDERR.
/**
* \attention This method may be very slow.
*/
static void Dump();
protected:
/// Checks whether a string is an option.
/**
* \param[in] pchStr text string
* \return true = option, false = otherwise
*/
static bool IsOption(const char* pchStr);
/// Checks whether a string is a long option.
/**
* This methos assumes the string is an option
* (if not the behavior is undefined).
*
* \param[in] pchStr text string
* \return true = option, false = otherwise
*/
static bool IsLongOption(const char* pchStr);
/// Parses a string and attempts to treat it as a long option.
/**
* \param[in] pchStr text string
* \param[out] rName option name
* \param[out] rVal value string
* \param[out] rfHasVal option has value yes/no
* \return true = success, false = failure
*/
static bool ParseLong(const char* pchStr, std::string& rName, std::string& rVal, bool& rfHasVal);
/// Parses a string and attempts to treat it as a short option.
/**
* \param[in] pchStr text string
* \param[out] rcName option name
* \param[out] rVal value string
* \param[out] rfHasVal option has value yes/no
*
* \attention This method assumes the string is a valid short option.
*/
static void ParseShort(const char* pchStr, char& rcName, std::string& rVal, bool& rfHasVal);
 
/// Dumps an option to STDERR.
/**
* \param[in] rName long option name
* \param[in] cShort short option name
* \param[in] pOpt option data
*/
static void DumpOption(const std::string& rName, char cShort, AppArgOption_t* pOpt);
private:
static size_t s_minCnt; ///< minimum value count
static size_t s_maxCnt; ///< maximum value count
 
static AA_LONG_MAP s_longMap; ///< mapping from long names to data
static AA_SHORT_MAP s_shortMap; ///< mapping from short names to data
static AA_VAL_LIST s_valList; ///< value list
};
 
 
#endif /*APPARGS_H_*/
/incron/trunk/CHANGELOG
1,3 → 1,14
0.5.0
* based on inotify-cxx 0.7.1
* bug related to events names fixed (#0000134)
* instance locking (only one instance allowed - also fixes #0000136)
* configuration introduced (including custom cfg. files)
* waiting for child processes rewritten to avoid race conditions
* user can request reloading the table (#0000130)
* new implementation for command line arguments
* some code refactoring
 
 
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)
/incron/trunk/incroncfg.cpp
0,0 → 1,195
 
/// inotify cron configuration implementation
/**
* \file incroncfg.cpp
*
* incron configuration
*
* Copyright (C) 2007 Lukas Jelinek, <lukas@aiken.cz>
*
* This program is free software; you can use it, redistribute
* it and/or modify it under the terms of the GNU General Public
* License, version 2 (see LICENSE-GPL).
*
*/
 
 
#include <fstream>
#include <sstream>
 
#include "incroncfg.h"
 
 
#define INCRON_CFG_DEFAULT "/etc/incron.conf"
 
 
typedef std::map<std::string, std::string> CFG_MAP;
typedef CFG_MAP::iterator CFG_ITER;
 
 
CFG_MAP IncronCfg::m_values;
CFG_MAP IncronCfg::m_defaults;
 
 
void IncronCfg::Init()
{
m_defaults.insert(CFG_MAP::value_type("system_table_dir", "/etc/incron.d"));
m_defaults.insert(CFG_MAP::value_type("user_table_dir", "/var/spool/incron"));
m_defaults.insert(CFG_MAP::value_type("allowed_users", "/etc/incron.allow"));
m_defaults.insert(CFG_MAP::value_type("denied_users", "/etc/incron.deny"));
m_defaults.insert(CFG_MAP::value_type("lockfile_dir", "/var/run"));
m_defaults.insert(CFG_MAP::value_type("lockfile_name", "incrond"));
m_defaults.insert(CFG_MAP::value_type("editor", ""));
}
 
void IncronCfg::Load(const std::string& rPath)
{
char s[1024];
std::ifstream is(rPath.c_str());
if (is.is_open()) {
while (!is.eof() && !is.fail()) {
is.getline(s, 1023);
std::string key, val;
if (ParseLine(s, key, val)) {
m_values.insert(CFG_MAP::value_type(key, val));
}
}
is.close();
return;
}
if (rPath == INCRON_CFG_DEFAULT)
return;
is.open(INCRON_CFG_DEFAULT);
if (is.is_open()) {
while (!is.eof() && !is.fail()) {
is.getline(s, 1023);
std::string key, val;
if (ParseLine(s, key, val)) {
m_values.insert(CFG_MAP::value_type(key, val));
}
}
is.close();
}
}
 
bool IncronCfg::GetValue(const std::string& rKey, std::string& rVal)
{
CFG_ITER it = m_values.find(rKey);
if (it != m_values.end()) {
rVal = (*it).second;
return true;
}
it = m_defaults.find(rKey);
if (it != m_defaults.end()) {
rVal = (*it).second;
return true;
}
return false;
}
 
bool IncronCfg::GetValue(const std::string& rKey, int& rVal)
{
std::string s;
if (GetValue(rKey, s)) {
if (sscanf(s.c_str(), "%i", &rVal) == 1)
return true;
}
return false;
}
 
bool IncronCfg::GetValue(const std::string& rKey, unsigned& rVal)
{
std::string s;
if (GetValue(rKey, s)) {
if (sscanf(s.c_str(), "%u", &rVal) == 1)
return true;
}
return false;
}
 
bool IncronCfg::GetValue(const std::string& rKey, bool& rVal)
{
std::string s;
if (GetValue(rKey, s)) {
size_t len = (size_t) s.length();
for (size_t i = 0; i < len; i++) {
s[i] = (char) tolower(s[i]);
}
rVal = (s == "1" || s == "true" || s == "yes" || s == "on" || s == "enable" || s == "enabled");
return true;
}
return false;
}
 
std::string IncronCfg::BuildPath(const std::string& rPath, const std::string& rName)
{
if (rPath.rfind('/') == rPath.length() - 1)
return rPath + rName;
return rPath + "/" + rName;
}
 
bool IncronCfg::ParseLine(const char* s, std::string& rKey, std::string& rVal)
{
// CAUTION: This code hasn't been optimized. It may be slow.
char key[1024], val[1024];
if (IsComment(s))
return false;
std::istringstream ss(s);
ss.get(key, 1023, '=');
if (ss.fail())
return false;
ss.get(val, 1023);
if (ss.fail())
return false;
rKey = key;
rVal = val;
std::string::size_type a = rKey.find_first_not_of(" \t");
std::string::size_type b = rKey.find_last_not_of(" \t");
if (a == std::string::npos || b == std::string::npos)
return false;
rKey = rKey.substr(a, b-a+1);
a = rVal.find_first_not_of(" \t=");
b = rVal.find_last_not_of(" \t");
if (a == std::string::npos || b == std::string::npos) {
rVal = "";
}
else {
rVal = rVal.substr(a, b-a+1);
}
return true;
}
 
bool IncronCfg::IsComment(const char* s)
{
char* sx = strchr(s, '#');
if (sx == NULL)
return false;
size_t len = sx - s;
for (size_t i = 0; i < len; i++) {
if (!(s[i] == ' ' || s[i] == '\t'))
return false;
}
return true;
}
 
/incron/trunk/README
40,7 → 40,12
 
The binaries must be of course installed as root.
 
If you want to use (after editting) the example configuration
file simply rename it from /etc/incron.conf.example to
/etc/incron.conf (you can also use -f <config> for one-time
use of a custom configuration file).
 
 
4. How to use
The incron daemon (incrond) must be run under root (typically from
runlevel script etc.). It loads the current user tables and hooks
88,7 → 93,10
/etc/incron.d and their commands use root privileges. System tables
are intended to be changed directly (without incrontab).
 
Some parameters of both incrontab and incrond can be changed by
the configuration. See the example file for more information.
 
 
5. Bugs, suggestions
THIS PROGRAM IS AN ALPHA VERSION. IT PROBABLY CONTAINS BUGS AND
THEREFORE IT IS NOT INTENDED FOR PRODUCTION USE.
/incron/trunk/incroncfg.h
0,0 → 1,129
 
/// inotify cron configuration header
/**
* \file incroncfg.h
*
* incron configuration
*
* Copyright (C) 2007 Lukas Jelinek, <lukas@aiken.cz>
*
* This program is free software; you can use it, redistribute
* it and/or modify it under the terms of the GNU General Public
* License, version 2 (see LICENSE-GPL).
*
*/
 
 
#ifndef INCRONCFG_H_
#define INCRONCFG_H_
 
 
#include <string>
#include <map>
 
/// Configuration class.
/**
* This class provides access to values loaded from
* a configuration file (either a explicitly specified one
* or the default one).
*/
class IncronCfg
{
public:
 
/// Initializes default configuration values.
static void Init();
 
/// Loads configuration values.
/**
* This method attempts to load configuration values
* from the specified file. If it fails (e.g. the file
* doesn't exist) the default file is read. As the last
* resort (for the case the default file can't be loaded)
* the hard-wired values are used.
*
* \param[in] rPath configuration file path
*/
static void Load(const std::string& rPath);
 
/// Retrieves a configuration value.
/**
* This method attempts to find the appropriate configuration
* value for the given key and stores it into the given
* variable.
*
* \param[in] rKey value key
* \param[out] rVal retrieved value
* \return true = success, false = failure (invalid key)
*/
static bool GetValue(const std::string& rKey, std::string& rVal);
/// Retrieves a configuration value.
/**
* This method attempts to find the appropriate configuration
* value for the given key and stores it into the given
* variable.
*
* \param[in] rKey value key
* \param[out] rVal retrieved value
* \return true = success, false = failure (invalid key)
*/
static bool GetValue(const std::string& rKey, int& rVal);
/// Retrieves a configuration value.
/**
* This method attempts to find the appropriate configuration
* value for the given key and stores it into the given
* variable.
*
* \param[in] rKey value key
* \param[out] rVal retrieved value
* \return true = success, false = failure (invalid key)
*/
static bool GetValue(const std::string& rKey, unsigned& rVal);
/// Retrieves a configuration value.
/**
* This method attempts to find the appropriate configuration
* value for the given key and stores it into the given
* variable.
*
* \param[in] rKey value key
* \param[out] rVal retrieved value
* \return true = success, false = failure (invalid key)
*/
static bool GetValue(const std::string& rKey, bool& rVal);
/// Builds a file path.
/**
* This function composes a path from a base path and a file name.
*
* \param[in] rPath base path
* \param[in] rName file name
* \return full path
*/
static std::string BuildPath(const std::string& rPath, const std::string& rName);
protected:
/// Parses a line a attempts to get a key and a value.
/**
* \param[in] s text line
* \param[out] rKey key
* \param[out] rVal value
* \return true = success, false = failure
*/
static bool ParseLine(const char* s, std::string& rKey, std::string& rVal);
/// Checks whether a line is a comment.
/**
* \param[in] s text line
* \return true = comment, false = otherwise
*/
static bool IsComment(const char* s);
 
private:
static std::map<std::string, std::string> m_values;
static std::map<std::string, std::string> m_defaults;
};
 
#endif /*INCRONCFG_H_*/
/incron/trunk/appinst.cpp
0,0 → 1,202
 
/// Application instance class implementation
/**
* \file appinst.cpp
*
* Copyright (C) 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:
*
* \li 1. X11-style license (see LICENSE-X11)
* \li 2. GNU Lesser General Public License, version 2.1 (see LICENSE-LGPL)
* \li 3. GNU General Public License, version 2 (see LICENSE-GPL)
*
* If you want to help with choosing the best license for you,
* please visit http://www.gnu.org/licenses/license-list.html.
*
*/
 
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
 
#include "appinst.h"
 
#ifdef APPINST_LOCK_DIRECTORY
#define LOCKDIR APPINST_LOCK_DIRECTORY
#else
#define LOCKDIR "/tmp"
#endif // APPINST_LOCK_DIRECTORY
 
 
AppInstance::AppInstance(const std::string& rName)
: m_name(rName),
m_fLocked(false)
{
}
 
AppInstance::~AppInstance()
{
try {
Unlock();
} catch (AppInstException e) {}
}
 
bool AppInstance::DoLock(const char* path)
{
int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (fd != -1) {
FILE* f = fdopen(fd, "w");
if (f == NULL) {
AppInstException e(errno);
close(fd);
throw e;
}
if (fprintf(f, "%u", (unsigned) getpid()) <= 0) {
AppInstException e(errno);
fclose(f);
throw e;
}
if (fclose(f) != 0)
throw AppInstException(errno);
m_fLocked = true;
return true;
}
if (errno != EEXIST)
throw AppInstException(errno);
return false;
}
 
bool AppInstance::Lock()
{
std::string fn = GetLockfile();
for (int i=0; i<100; i++) {
if (DoLock(fn.c_str()))
return true;
FILE* f = fopen(fn.c_str(), "r");
if (f == NULL) {
if (errno != ENOENT)
throw AppInstException(errno);
}
else {
unsigned pid;
ssize_t len = fscanf(f, "%u", &pid);
if (len == -1) {
AppInstException e(errno);
fclose(f);
throw e;
}
else if (len == 0) {
AppInstException e(EIO);
fclose(f);
throw e;
}
fclose(f);
int res = kill((pid_t) pid, 0);
if (res == 0)
return false;
if (errno != ESRCH)
throw AppInstException(errno);
res = unlink(fn.c_str());
if (res != 0 && errno != ENOENT)
throw AppInstException(errno);
}
}
return false;
}
 
void AppInstance::Unlock()
{
if (!m_fLocked)
return;
if (unlink(GetLockfile().c_str()) != 0 && errno != ENOENT)
throw AppInstException(errno);
m_fLocked = false;
}
 
bool AppInstance::Exists() const
{
if (m_fLocked)
return true;
FILE* f = fopen(GetLockfile().c_str(), "r");
if (f == NULL) {
if (errno == ENOENT)
return false;
else
throw AppInstException(errno);
}
bool ok = false;
unsigned pid;
if (fscanf(f, "%u", &pid) == 1) {
if (kill((pid_t) pid, 0) == 0)
ok = true;
else if (errno != ESRCH) {
AppInstException e(errno);
fclose(f);
throw e;
}
}
fclose(f);
return ok;
}
 
bool AppInstance::SendSignal(int iSigNo) const
{
FILE* f = fopen(GetLockfile().c_str(), "r");
if (f == NULL) {
if (errno == ENOENT)
return false;
else
throw AppInstException(errno);
}
bool ok = false;
unsigned pid;
if (fscanf(f, "%u", &pid) == 1) {
if (pid != (unsigned) getpid()) {
if (kill((pid_t) pid, iSigNo) == 0) {
ok = true;
}
else if (errno != ESRCH) {
AppInstException e(errno);
fclose(f);
throw e;
}
}
}
fclose(f);
return ok;
}
 
std::string AppInstance::GetLockfile() const
{
return std::string(LOCKDIR) + "/" + m_name + ".pid";
}
/incron/trunk/incrontab.cpp
21,13 → 21,15
#include "inotify-cxx.h"
 
#include "incrontab.h"
#include "incroncfg.h"
 
 
/*
/// Allowed users
#define INCRON_ALLOW_PATH "/etc/incron.allow"
 
/// Denied users
#define INCRON_DENY_PATH "/etc/incron.deny"
*/
 
/*
* ALLOW/DENY SEMANTICS
44,7 → 46,7
 
 
 
InCronTabEntry::InCronTabEntry()
IncronTabEntry::IncronTabEntry()
: m_uMask(0),
m_fNoLoop(false)
{
51,7 → 53,7
}
 
InCronTabEntry::InCronTabEntry(const std::string& rPath, uint32_t uMask, const std::string& rCmd)
IncronTabEntry::IncronTabEntry(const std::string& rPath, uint32_t uMask, const std::string& rCmd)
: m_path(rPath),
m_uMask(uMask),
m_cmd(rCmd)
59,7 → 61,7
}
 
std::string InCronTabEntry::ToString() const
std::string IncronTabEntry::ToString() const
{
std::ostringstream ss;
78,7 → 80,7
return ss.str();
}
 
bool InCronTabEntry::Parse(const std::string& rStr, InCronTabEntry& rEntry)
bool IncronTabEntry::Parse(const std::string& rStr, IncronTabEntry& rEntry)
{
unsigned long u;
std::string s1, s2, s3;
123,7 → 125,7
return true;
}
 
std::string InCronTabEntry::GetSafePath(const std::string& rPath)
std::string IncronTabEntry::GetSafePath(const std::string& rPath)
{
std::ostringstream stream;
143,7 → 145,7
return stream.str();
}
 
bool InCronTab::Load(const std::string& rPath)
bool IncronTab::Load(const std::string& rPath)
{
m_tab.clear();
152,9 → 154,9
return false;
char s[1000];
InCronTabEntry e;
IncronTabEntry e;
while (fgets(s, 1000, f) != NULL) {
if (InCronTabEntry::Parse(s, e)) {
if (IncronTabEntry::Parse(s, e)) {
m_tab.push_back(e);
}
}
164,13 → 166,13
return true;
}
 
bool InCronTab::Save(const std::string& rPath)
bool IncronTab::Save(const std::string& rPath)
{
FILE* f = fopen(rPath.c_str(), "w");
if (f == NULL)
return false;
std::deque<InCronTabEntry>::iterator it = m_tab.begin();
std::deque<IncronTabEntry>::iterator it = m_tab.begin();
while (it != m_tab.end()) {
fputs((*it).ToString().c_str(), f);
fputs("\n", f);
182,14 → 184,21
return true;
}
 
bool InCronTab::CheckUser(const std::string& rUser)
bool IncronTab::CheckUser(const std::string& rUser)
{
char s[100], u[100];
FILE* f = fopen(INCRON_ALLOW_PATH, "r");
std::string path;
if (!IncronCfg::GetValue("allowed_users", path))
throw InotifyException("configuration is corrupted", EINVAL);
FILE* f = fopen(path.c_str(), "r");
if (f == NULL) {
if (errno == ENOENT) {
f = fopen(INCRON_DENY_PATH, "r");
if (!IncronCfg::GetValue("denied_users", path))
throw InotifyException("configuration is corrupted", EINVAL);
f = fopen(path.c_str(), "r");
if (f == NULL) {
return errno == ENOENT;
}
221,18 → 230,22
return false;
}
 
std::string InCronTab::GetUserTablePath(const std::string& rUser)
std::string IncronTab::GetUserTablePath(const std::string& rUser)
{
std::string s(INCRON_USER_TABLE_BASE);
s.append(rUser);
return s;
std::string s;
if (!IncronCfg::GetValue("user_table_dir", s))
throw InotifyException("configuration is corrupted", EINVAL);
return IncronCfg::BuildPath(s, rUser);
}
 
std::string InCronTab::GetSystemTablePath(const std::string& rName)
std::string IncronTab::GetSystemTablePath(const std::string& rName)
{
std::string s(INCRON_SYS_TABLE_BASE);
s.append(rName);
return s;
std::string s;
if (!IncronCfg::GetValue("system_table_dir", s))
throw InotifyException("configuration is corrupted", EINVAL);
return IncronCfg::BuildPath(s, rName);
}
 
 
/incron/trunk/incrond.8
1,21 → 1,21
.TH "incrond" "8" "0.4.0" "Lukas Jelinek" "incron documentation"
.TH "incrond" "8" "0.5.0" "Lukas Jelinek" "incron documentation"
.SH "NAME"
incrond \- inotify cron (incron) daemon
 
.SH "SYNOPSIS"
\fBincrond\fR [ \fB\-n\fR | \fB\-k\fR ]
\fBincrond\fR [ <options> ]
.SH "DESCRIPTION"
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). It can be run on foreground too.
 
\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.
\fIincrond\fR uses two categories of tables \fIincrontab\fR(5). System tables are usually 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.
User tables are located in /var/spool/incron by default 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.
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. Location of these files can be changed in the configuration.
 
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.
 
22,7 → 22,9
 
\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).
\fB\-k\fR (or \fB\-\-kill\fR) option terminates a running instance of \fBincrond\fR.
 
\fB\-f <FILE>\fR (or \fB\-\-config=<FILE>\fR) option specifies another location for the configuration file (/etc/incron.conf is used by default).
.SH "SEE ALSO"
incrontab(1), incrontab(5)
.SH "BUGS"
/incron/trunk/icd-main.cpp
25,10 → 25,13
#include <sys/stat.h>
 
#include "inotify-cxx.h"
#include "appinst.h"
#include "appargs.h"
 
#include "incron.h"
#include "incrontab.h"
#include "usertable.h"
#include "incroncfg.h"
 
 
/// Logging options (console as fallback, log PID)
37,19 → 40,28
/// 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>"
/// incrond version string
#define INCROND_VERSION INCROND_NAME " " INCRON_VERSION
 
#define INCROND_VERSION_TEXT INCRON_DAEMON_NAME " " INCRON_VERSION
/// incrontab description string
#define INCROND_DESCRIPTION "incrond - inotify cron daemon\n" \
"(c) Lukas Jelinek, 2006, 2007"
 
/// incrontab help string
#define INCROND_HELP INCROND_DESCRIPTION "\n\n" \
"usage: incrond [<options>]\n\n" \
"<operation> may be one of the following:\n" \
"These options may be used:\n" \
" -?, --about gives short information about program\n" \
" -h, --help prints this help text\n" \
" -n, --foreground runs on foreground (no daemonizing)\n" \
" -k, --kill terminates running instance of incrond\n" \
" -f <FILE>, --config=<FILE> overrides default configuration file (requires root privileges)\n" \
" -V, --version prints program version\n\n" \
"For reporting bugs please use http:://bts.aiken.cz\n"
 
 
 
/// User name to user table mapping table
SUT_MAP g_ut;
 
94,6 → 106,8
}
 
 
 
 
/// Attempts to load all user incron tables.
/**
* Loaded tables are registered for processing events.
106,7 → 120,11
{
// WARNING - this function has not been optimized!!!
DIR* d = opendir(INCRON_SYS_TABLE_BASE);
std::string s;
if (!IncronCfg::GetValue("system_table_dir", s))
throw InotifyException("configuration system is corrupted", EINVAL);
DIR* d = opendir(s.c_str());
if (d != NULL) {
syslog(LOG_NOTICE, "loading system tables");
113,11 → 131,12
struct dirent* pDe = NULL;
while ((pDe = readdir(d)) != NULL) {
std::string un(pDe->d_name);
std::string path(IncronCfg::BuildPath(s, 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)
if (stat(path.c_str(), &st) == 0)
ok = S_ISREG(st.st_mode);
}
124,7 → 143,7
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));
g_ut.insert(SUT_MAP::value_type(path, pUt));
pUt->Load();
}
}
135,8 → 154,10
syslog(LOG_WARNING, "cannot open system table directory (ignoring)");
}
d = opendir(INCRON_USER_TABLE_BASE);
if (!IncronCfg::GetValue("user_table_dir", s))
throw InotifyException("configuration system is corrupted", EINVAL);
d = opendir(s.c_str());
if (d == NULL)
throw InotifyException("cannot open user table directory", errno);
145,11 → 166,12
struct dirent* pDe = NULL;
while ((pDe = readdir(d)) != NULL) {
std::string un(pDe->d_name);
std::string path(IncronCfg::BuildPath(s, 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)
if (stat(path.c_str(), &st) == 0)
ok = S_ISREG(st.st_mode);
}
157,7 → 179,7
if (UserTable::CheckUser(pDe->d_name)) {
syslog(LOG_INFO, "loading table for user %s", pDe->d_name);
UserTable* pUt = new UserTable(pEd, un, false);
g_ut.insert(SUT_MAP::value_type(std::string(INCRON_USER_TABLE_BASE) + un, pUt));
g_ut.insert(SUT_MAP::value_type(path, pUt));
pUt->Load();
}
else {
219,71 → 241,9
|| 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[out] pfd poll structure array
* \param[in] pipefd pipe file descriptor
* \param[in] infd inotify infrastructure file descriptor
*/
308,48 → 268,113
*/
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");
AppArgs::Init();
 
if (!( AppArgs::AddOption("about", '?', AAT_NO_VALUE, false)
&& AppArgs::AddOption("help", 'h', AAT_NO_VALUE, false)
&& AppArgs::AddOption("foreground", 'n', AAT_NO_VALUE, false)
&& AppArgs::AddOption("kill", 'k', AAT_NO_VALUE, false)
&& AppArgs::AddOption("config", 'f', AAT_MANDATORY_VALUE, false))
&& AppArgs::AddOption("version", 'V', AAT_NO_VALUE, false))
{
fprintf(stderr, "error while initializing application");
return 1;
}
if (argc == 2) {
if (check_parameter(argv[1], "-h", "--help")) {
printf("%s\n", INCROND_HELP_TEXT);
AppArgs::Parse(argc, argv);
if (AppArgs::ExistsOption("help")) {
fprintf(stderr, "%s\n", INCROND_HELP);
return 0;
}
if (AppArgs::ExistsOption("about")) {
fprintf(stderr, "%s\n", INCROND_DESCRIPTION);
return 0;
}
if (AppArgs::ExistsOption("version")) {
fprintf(stderr, "%s\n", INCROND_VERSION);
return 0;
}
AppInstance app("incrond");
IncronCfg::Init();
if (AppArgs::ExistsOption("kill")) {
fprintf(stderr, "attempting to terminate a running instance of incrond...\n");
if (app.SendSignal(SIGTERM)) {
fprintf(stderr, "instance(s) notified, going down\n");
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]);
else {
fprintf(stderr, "error - incrond probably not running\n");
return 1;
}
}
openlog(INCRON_DAEMON_NAME, INCRON_LOG_OPTS, INCRON_LOG_FACIL);
if (AppArgs::ExistsOption("foreground"))
g_daemon = false;
openlog(INCROND_NAME, INCRON_LOG_OPTS, INCRON_LOG_FACIL);
syslog(LOG_NOTICE, "starting service (version %s, built on %s %s)", INCRON_VERSION, __DATE__, __TIME__);
std::string cfg;
if (!AppArgs::GetOption("config", cfg))
cfg = INCRON_CONFIG;
IncronCfg::Load(cfg);
AppArgs::Destroy();
int ret = 0;
std::string sysBase;
std::string userBase;
if (!IncronCfg::GetValue("system_table_dir", sysBase))
throw InotifyException("configuration is corrupted", EINVAL);
if (access(sysBase.c_str(), R_OK) != 0) {
syslog(LOG_CRIT, "cannot read directory for system tables (%s): (%i) %s", sysBase.c_str(), errno, strerror(errno));
if (!g_daemon)
fprintf(stderr, "cannot read directory for system tables (%s): (%i) %s", sysBase.c_str(), errno, strerror(errno));
ret = 1;
goto error;
}
if (!IncronCfg::GetValue("user_table_dir", userBase))
throw InotifyException("configuration is corrupted", EINVAL);
if (access(userBase.c_str(), R_OK) != 0) {
syslog(LOG_CRIT, "cannot read directory for user tables (%s): (%i) %s", userBase.c_str(), errno, strerror(errno));
if (!g_daemon)
fprintf(stderr, "cannot read directory for user tables (%s): (%i) %s", userBase.c_str(), errno, strerror(errno));
ret = 1;
goto error;
}
try {
if (g_daemon)
daemon(0, 0);
try {
if (!app.Lock()) {
syslog(LOG_CRIT, "another instance of incrond already running");
if (!g_daemon)
fprintf(stderr, "another instance of incrond already running\n");
ret = 1;
goto error;
}
} catch (AppInstException e) {
syslog(LOG_CRIT, "instance lookup failed: (%i) %s", e.GetErrorNumber(), strerror(e.GetErrorNumber()));
if (!g_daemon)
fprintf(stderr, "instance lookup failed: (%i) %s\n", e.GetErrorNumber(), strerror(e.GetErrorNumber()));
ret = 1;
goto error;
}
prepare_pipe();
358,9 → 383,9
in.SetCloseOnExec(true);
uint32_t wm = IN_CLOSE_WRITE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_UNMOUNT;
InotifyWatch stw(INCRON_SYS_TABLE_BASE, wm);
InotifyWatch stw(sysBase, wm);
in.Add(stw);
InotifyWatch utw(INCRON_USER_TABLE_BASE, wm);
InotifyWatch utw(userBase, wm);
in.Add(utw);
EventDispatcher ed(g_cldPipe[0], &in, &stw, &utw);
370,9 → 395,8
} catch (InotifyException e) {
int err = e.GetErrorNumber();
syslog(LOG_CRIT, "%s: (%i) %s", e.GetMessage().c_str(), err, strerror(err));
syslog(LOG_NOTICE, "stopping service");
closelog();
return 1;
ret = 1;
goto error;
}
signal(SIGTERM, on_signal);
393,75 → 417,7
if (errno != EINTR)
throw InotifyException("polling failed", errno, NULL);
}
/*
while (in.GetEvent(e)) {
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(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());
delete pUt;
g_ut.erase(it);
}
}
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(), false);
g_ut.insert(SUT_MAP::value_type(std::string(INCRON_USER_TABLE_BASE) + e.GetName(), pUt));
pUt->Load();
}
}
}
}
else {
ed.DispatchEvent(e);
}
}
*/
}
if (g_cldPipe[0] != -1)
473,11 → 429,14
syslog(LOG_CRIT, "*** unhandled exception occurred ***");
syslog(LOG_CRIT, " %s", e.GetMessage().c_str());
syslog(LOG_CRIT, " error: (%i) %s", err, strerror(err));
ret = 1;
}
 
error:
 
syslog(LOG_NOTICE, "stopping service");
closelog();
return 0;
return ret;
}
/incron/trunk/appinst.h
0,0 → 1,121
 
/// Application instance class header
/**
* \file appinst.h
*
* Copyright (C) 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:
*
* \li 1. X11-style license (see LICENSE-X11)
* \li 2. GNU Lesser General Public License, version 2.1 (see LICENSE-LGPL)
* \li 3. GNU General Public License, version 2 (see LICENSE-GPL)
*
* If you want to help with choosing the best license for you,
* please visit http://www.gnu.org/licenses/license-list.html.
*
*/
 
 
#ifndef APPINST_H_
#define APPINST_H_
 
 
#include <string>
 
 
/// Exception class.
/**
* This class provides information about occurred errors.
*/
class AppInstException
{
public:
/// Constructor.
/**
* \param[in] iErr error number
*/
AppInstException(int iErr) : m_iErr(iErr) {}
/// Returns the error number.
/**
* \return error number
*/
inline int GetErrorNumber() const
{
return m_iErr;
}
private:
int m_iErr; ///< error number
};
 
/// Application instance management class.
/**
* This class is intended for application which require to
* be running only once (one instance only). It provides some
* methods for simple locking, signaling etc.
*/
class AppInstance
{
public:
/// Constructor.
/**
* \param[in] rName application name
*/
AppInstance(const std::string& rName);
/// Destructor.
~AppInstance();
/// Attempts to lock the instance.
/**
* This method attempts to create a lockfile. If the file
* already exists it checks whether its owner is still living.
* If it does this method fails. Otherwise it unlinks this file
* and re-attempts to create it.
*
* \return true = instance locked, false = otherwise
*/
bool Lock();
/// Unlocks the instance.
/**
* This method removes (unlinks) the appropriate lockfile.
* If the instance hasn't been locked this method has no
* effect.
*/
void Unlock();
/// Checks whether an instance of this application exists.
/**
* If this instance has acquired the lockfile the call will
* be successful. Otherwise it checks for existence of
* another running instance.
*
* \return true = instance exists, false = otherwise
*/
bool Exists() const;
/// Sends a signal to an instance of this application.
/**
* This method doesn't signal the current instance.
*
* \param[in] iSigNo signal number
* \return true = success, false = otherwise
*/
bool SendSignal(int iSigNo) const;
protected:
bool DoLock(const char* path);
 
std::string GetLockfile() const;
private:
std::string m_name;
bool m_fLocked;
};
 
#endif /*APPINST_H_*/
/incron/trunk/incrontab.h
22,15 → 22,16
 
#include "strtok.h"
 
/*
/// 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
class IncronTabEntry
{
public:
/// Constructor.
39,7 → 40,7
*
* \sa Parse()
*/
InCronTabEntry();
IncronTabEntry();
 
/// Constructor.
/**
49,10 → 50,10
* \param[in] uMask event mask
* \param[in] rCmd command string
*/
InCronTabEntry(const std::string& rPath, uint32_t uMask, const std::string& rCmd);
IncronTabEntry(const std::string& rPath, uint32_t uMask, const std::string& rCmd);
/// Destructor.
~InCronTabEntry() {}
~IncronTabEntry() {}
/// Converts the entry to string representation.
/**
68,7 → 69,7
* \param[out] rEntry parametrized entry
* \return true = success, false = failure
*/
static bool Parse(const std::string& rStr, InCronTabEntry& rEntry);
static bool Parse(const std::string& rStr, IncronTabEntry& rEntry);
/// Returns the watch filesystem path.
/**
130,20 → 131,20
 
 
/// Incron table class.
class InCronTab
class IncronTab
{
public:
/// Constructor.
InCronTab() {}
IncronTab() {}
/// Destructor.
~InCronTab() {}
~IncronTab() {}
/// Add an entry to the table.
/**
* \param[in] rEntry table entry
*/
inline void Add(const InCronTabEntry& rEntry)
inline void Add(const IncronTabEntry& rEntry)
{
m_tab.push_back(rEntry);
}
180,7 → 181,7
* pass an invalid value the program may crash
* and/or behave unpredictible way!
*/
inline InCronTabEntry& GetEntry(int index)
inline IncronTabEntry& GetEntry(int index)
{
return m_tab[index];
}
225,7 → 226,7
static std::string GetSystemTablePath(const std::string& rName);
 
protected:
std::deque<InCronTabEntry> m_tab; ///< incron table
std::deque<IncronTabEntry> m_tab; ///< incron table
};
 
 
/incron/trunk/incron.conf.example
0,0 → 1,72
#
# *** incron example configuration file ***
#
# (c) Lukas Jelinek, 2007
#
 
 
# Parameter: system_table_dir
# Meaning: system table directory
# Description: This directory is examined by incrond for system table files.
# Default: /etc/incron.d
#
# Example:
# system_table_dir = /var/spool/incron.systables
 
 
# Parameter: user_table_dir
# Meaning: user table directory
# Description: This directory is examined by incrond for user table files.
# Default: /var/spool/incron
#
# Example:
# user_table_dir = /var/spool/incron.usertables
 
 
# Parameter: allowed_users
# Meaning: allowed users list file
# Description: This file contains users allowed to use incron.
# Default: /etc/incron.allow
#
# Example:
# allowed_users = /etc/incron/allow
 
 
# Parameter: denied_users
# Meaning: denied users list file
# Description: This file contains users denied to use incron.
# Default: /etc/incron.deny
#
# Example:
# allowed_users = /etc/incron/deny
 
 
# Parameter: lockfile_dir
# Meaning: application lock file directory
# Description: This directory is used for creating a lock avoiding to run
# multiple instances of incrond.
# Default: /var/run
#
# Example:
# lockfile_dir = /tmp
 
 
# Parameter: lockfile_name
# Meaning: application lock file name base
# Description: This name (appended by '.pid') is used for creating a lock
# avoiding to run multiple instances of incrond.
# Default: incrond
#
# Example:
# lockfile_name = incron.lock
 
 
# Parameter: editor
# Meaning: editor executable
# Description: This name or path is used to run as an editor for editting
# user tables.
# Default: vim
#
# Example:
# editor = nano
 
/incron/trunk/inotify-cxx.h
246,8 → 246,11
if (pEvt != NULL) {
m_uMask = (uint32_t) pEvt->mask;
m_uCookie = (uint32_t) pEvt->cookie;
if (pEvt->name != NULL)
m_name = pEvt->name;
if (pEvt->name != NULL) {
m_name = pEvt->len > 0
? pEvt->name
: "";
}
m_pWatch = pWatch;
}
else {
/incron/trunk/usertable.cpp
23,6 → 23,7
#include <sys/stat.h>
 
#include "usertable.h"
#include "incroncfg.h"
 
#ifdef IN_DONT_FOLLOW
#define DONT_FOLLOW(mask) InotifyEvent::IsType(mask, IN_DONT_FOLLOW)
31,7 → 32,7
#endif // IN_DONT_FOLLOW
 
 
PROC_LIST UserTable::s_procList;
PROC_MAP UserTable::s_procMap;
 
extern volatile bool g_fFinish;
extern SUT_MAP g_ut;
164,7 → 165,7
g_fFinish = true;
}
else if (!e.GetName().empty()) {
SUT_MAP::iterator it = g_ut.find(m_pSys->GetPath() + e.GetName());
SUT_MAP::iterator it = g_ut.find(IncronCfg::BuildPath(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)) {
181,7 → 182,7
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));
g_ut.insert(SUT_MAP::value_type(IncronTab::GetSystemTablePath(e.GetName()), pUt));
pUt->Load();
}
}
192,7 → 193,7
g_fFinish = true;
}
else if (!e.GetName().empty()) {
SUT_MAP::iterator it = g_ut.find(m_pUser->GetPath() + e.GetName());
SUT_MAP::iterator it = g_ut.find(IncronCfg::BuildPath(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)) {
210,7 → 211,7
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));
g_ut.insert(SUT_MAP::value_type(IncronTab::GetUserTablePath(e.GetName()), pUt));
pUt->Load();
}
}
240,12 → 241,12
void UserTable::Load()
{
m_tab.Load(m_fSysTable
? InCronTab::GetSystemTablePath(m_user)
: InCronTab::GetUserTablePath(m_user));
? IncronTab::GetSystemTablePath(m_user)
: IncronTab::GetUserTablePath(m_user));
int cnt = m_tab.GetCount();
for (int i=0; i<cnt; i++) {
InCronTabEntry& rE = m_tab.GetEntry(i);
IncronTabEntry& rE = m_tab.GetEntry(i);
InotifyWatch* pW = new InotifyWatch(rE.GetPath(), rE.GetMask());
// warning only - permissions may change later
275,6 → 276,19
while (it != m_map.end()) {
InotifyWatch* pW = (*it).first;
m_in.Remove(pW);
PROC_MAP::iterator it2 = s_procMap.begin();
while (it2 != s_procMap.end()) {
if ((*it2).second.pWatch == pW) {
PROC_MAP::iterator it3 = it2;
it2++;
s_procMap.erase(it3);
}
else {
it2++;
}
}
delete pW;
it++;
}
285,7 → 299,7
void UserTable::OnEvent(InotifyEvent& rEvt)
{
InotifyWatch* pW = rEvt.GetWatch();
InCronTabEntry* pE = FindEntry(pW);
IncronTabEntry* pE = FindEntry(pW);
// no entry found - this shouldn't occur
if (pE == NULL)
357,9 → 371,8
if (pE->IsNoLoop())
pW->SetEnabled(false);
ProcData_t pd;
pd.pid = fork();
if (pd.pid == 0) {
pid_t pid = fork();
if (pid == 0) {
// for system table
if (m_fSysTable) {
382,7 → 395,8
}
}
}
else if (pd.pid > 0) {
else if (pid > 0) {
ProcData_t pd;
if (pE->IsNoLoop()) {
pd.onDone = on_proc_done;
pd.pWatch = pW;
392,7 → 406,7
pd.pWatch = NULL;
}
s_procList.push_back(pd);
s_procMap.insert(PROC_MAP::value_type(pid, pd));
}
else {
if (pE->IsNoLoop())
404,7 → 418,7
CleanupArgs(argc, argv);
}
 
InCronTabEntry* UserTable::FindEntry(InotifyWatch* pWatch)
IncronTabEntry* UserTable::FindEntry(InotifyWatch* pWatch)
{
IWCE_MAP::iterator it = m_map.find(pWatch);
if (it == m_map.end())
452,19 → 466,16
 
void UserTable::FinishDone()
{
PROC_LIST::iterator it = s_procList.begin();
while (it != s_procList.end()) {
ProcData_t& pd = *it;
int status = 0;
int res = waitpid(pd.pid, &status, WNOHANG);
if (res == pd.pid && (WIFEXITED(status) || WIFSIGNALED(status))) {
pid_t res = 0;
int status = 0;
while ((res = waitpid(-1, &status, WNOHANG)) > 0) {
PROC_MAP::iterator it = s_procMap.find(res);
if (it != s_procMap.end()) {
ProcData_t pd = (*it).second;
if (pd.onDone != NULL)
(*pd.onDone)(pd.pWatch);
it = s_procList.erase(it);
s_procMap.erase(it);
}
else {
it++;
}
}
}
 
/incron/trunk/usertable.h
35,7 → 35,6
/// Child process data
typedef struct
{
pid_t pid; ///< PID
proc_done_cb onDone; ///< function called after process finishes
InotifyWatch* pWatch; ///< related watch
} ProcData_t;
44,10 → 43,10
typedef std::map<int, UserTable*> FDUT_MAP;
 
/// Watch-to-tableentry mapping
typedef std::map<InotifyWatch*, InCronTabEntry*> IWCE_MAP;
typedef std::map<InotifyWatch*, IncronTabEntry*> IWCE_MAP;
 
/// Child process list
typedef std::deque<ProcData_t> PROC_LIST;
typedef std::map<pid_t, ProcData_t> PROC_MAP;
 
/// Event dispatcher class.
/**
213,7 → 212,7
if (pw == NULL)
return false;
return InCronTab::CheckUser(user);
return IncronTab::CheckUser(user);
}
private:
221,10 → 220,10
EventDispatcher* m_pEd; ///< event dispatcher
std::string m_user; ///< user name
bool m_fSysTable; ///< system table yes/no
InCronTab m_tab; ///< incron table
IncronTab m_tab; ///< incron table
IWCE_MAP m_map; ///< watch-to-entry mapping
 
static PROC_LIST s_procList; ///< child process list
static PROC_MAP s_procMap; ///< child process mapping
/// Finds an entry for a watch.
/**
231,7 → 230,7
* \param[in] pWatch inotify watch
* \return pointer to the appropriate entry; NULL if no such entry exists
*/
InCronTabEntry* FindEntry(InotifyWatch* pWatch);
IncronTabEntry* FindEntry(InotifyWatch* pWatch);
/// Prepares arguments for creating a child process.
/**
/incron/trunk/Makefile
2,6 → 2,7
PREFIX = /usr/local
USERDATADIR = /var/spool/incron
SYSDATADIR = /etc/incron.d
CFGDIR = /etc
MANPATH = /usr/share/man
RELEASE = incron-`cat VERSION`
RELEASEDIR = /tmp/$(RELEASE)
16,14 → 17,13
WARNINGS = -Wall
CXXAUX = -pipe
 
CPPFLAGS =
CXXFLAGS = $(OPTIMIZE) $(DEBUG) $(WARNINGS) $(CXXAUX)
LDFLAGS = $(WARNINGS)
 
PROGRAMS = incrond incrontab
 
INCROND_OBJ = icd-main.o incrontab.o inotify-cxx.o usertable.o strtok.o
INCRONTAB_OBJ = ict-main.o incrontab.o inotify-cxx.o strtok.o
INCROND_OBJ = icd-main.o incrontab.o inotify-cxx.o usertable.o strtok.o appinst.o incroncfg.o appargs.o
INCRONTAB_OBJ = ict-main.o incrontab.o inotify-cxx.o strtok.o incroncfg.o appargs.o
 
 
all: $(PROGRAMS)
49,6 → 49,7
$(INSTALL) -m 0755 incrond $(PREFIX)/sbin/
$(INSTALL) -m 0755 -o $(USER) -d $(USERDATADIR)
$(INSTALL) -m 0755 -o $(USER) -d $(SYSDATADIR)
$(INSTALL) -m 0644 -o $(USER) incron.conf.example $(CFGDIR)
 
install-man: incrontab.1 incrontab.5 incrond.8
$(INSTALL) -m 0755 -d $(MANPATH)/man1
62,6 → 63,7
[ -d $(PREFIX) ]
rm -f $(PREFIX)/bin/incrontab
rm -f $(PREFIX)/sbin/incrond
rm -f $(CFGDIR)/incron.conf.example
 
uninstall-man:
rm -f $(MANPATH)/man1/incrontab.1
76,6 → 78,7
cp -r doc $(RELEASEDIR)
cp *.h $(RELEASEDIR)
cp *.cpp $(RELEASEDIR)
cp incron.conf.example $(RELEASEDIR)
cp Makefile CHANGELOG COPYING LICENSE-GPL LICENSE-LGPL LICENSE-X11 README TODO VERSION $(RELEASEDIR)
cp incrond.8 incrontab.1 incrontab.5 $(RELEASEDIR)
tar -c -f $(RELEASE).tar -C $(RELEASEDIR)/.. $(RELEASE)
103,10 → 106,12
 
.POSIX:
 
icd-main.o: icd-main.cpp inotify-cxx.h incrontab.h usertable.h incron.h
icd-main.o: icd-main.cpp inotify-cxx.h incrontab.h usertable.h incron.h appinst.h incroncfg.h appargs.h
incrontab.o: incrontab.cpp incrontab.h inotify-cxx.h strtok.h
inotify-cxx.o: inotify-cxx.cpp inotify-cxx.h
usertable.o: usertable.cpp usertable.h strtok.h
ict-main.o: ict-main.cpp incrontab.h incron.h
ict-main.o: ict-main.cpp incrontab.h incron.h incroncfg.h appargs.h
strtok.o: strtok.cpp strtok.h
 
appinst.o: appinst.cpp appinst.h
incroncfg.o: incroncfg.cpp incroncfg.h
appargs.o: appargs.cpp appargs.h
/incron/trunk/incrontab.1
1,10 → 1,10
.TH "incrontab" "1" "0.4.0" "Lukas Jelinek" "incron documentation"
.TH "incrontab" "1" "0.5.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\-f\fR \fIconfig\fR] \fIfile\fR
 
\fBincrontab\fR [\fB\-u\fR \fIuser\fR] [\fB\-l\fR | \fB\-r\fR | \fB\-e\fR | \fB\-t\fR]
\fBincrontab\fR [\fB\-u\fR \fIuser\fR] [\fB\-f\fR \fIconfig\fR] [\fB\-l\fR | \fB\-r\fR | \fB\-e\fR | \fB\-t\fR | \fB\-d\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)).
 
12,7 → 12,7
 
All informational messages of this program are printed to the standard error output (stderr).
 
If \fI/etc/incron.allow\fR exists only users listed here may use incron. Otherwise if \fI/etc/incron.deny\fR exists only users NOT listed here may use incron. If none of these files exists everyone is allowed to use incron. (\fBImportant note:\fR This behavior is insecure and will be probably changed to be compatible with the style used by ISC Cron.)
If \fI/etc/incron.allow\fR exists only users listed here may use incron. Otherwise if \fI/etc/incron.deny\fR exists only users NOT listed here may use incron. If none of these files exists everyone is allowed to use incron. (\fBImportant note:\fR This behavior is insecure and will be probably changed to be compatible with the style used by ISC Cron.) Location of this files can be changed in the configuration.
 
The first form of this command imports a file, validates it and stores to the table. "\-" can be used for loading from the standard input.
 
24,9 → 24,25
 
\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\-e\fR (or \fB\-\-edit\fR) option causes executing an editor for editting the user table (see below for the information about editor selection). You can edit your incron table now. If the table is changed it stores the modified version.
 
\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.
 
\fB\-f <FILE>\fR (or \fB\-\-config=<FILE>\fR) option specifies another location for the configuration file (/etc/incron.conf is used by default). This feature requires root privileges.
 
There is a few complex algorithm how to determine which editor will be user for editting. If any of the following rule succeeds the appropriate editor is used:
 
1. EDITOR environment variable
 
2. VISUAL environment variable
 
3. configuration value
 
4. etc/alternatives/editor
 
5. hard\-wired editor (\fIvim\fR by default)
 
It's not recommended to use graphical editors (such as gVim, KEdit etc.) due to possible problems with connecting to the X server.
.SH "SEE ALSO"
incrond(8), incrontab(5)
.SH "AUTHOR"
/incron/trunk/incron.h
21,16 → 21,19
#define INCRON_NAME "incron"
 
/// Daemon name
#define INCRON_DAEMON_NAME "incrond"
#define INCROND_NAME "incrond"
 
/// Table manipulator name
#define INCRON_TAB_NAME "incrontab"
#define INCRONTAB_NAME "incrontab"
 
/// Application version (release)
#define INCRON_VERSION "0.4.0"
#define INCRON_VERSION "0.5.0"
 
/// Address for sending bugs
#define INCRON_BUG_ADDRESS "<bugs@aiken.cz>"
 
/// Default configuration file
#define INCRON_CONFIG "/etc/incron.conf"
 
 
#endif //_INCRON_H_
/incron/trunk/appargs.cpp
0,0 → 1,271
 
/// application arguments processor implementation
/**
* \file appargs.cpp
*
* application arguments processor
*
* Copyright (C) 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:
*
* \li 1. X11-style license (see LICENSE-X11)
* \li 2. GNU Lesser General Public License, version 2.1 (see LICENSE-LGPL)
* \li 3. GNU General Public License, version 2 (see LICENSE-GPL)
*
* If you want to help with choosing the best license for you,
* please visit http://www.gnu.org/licenses/license-list.html.
*
*/
 
 
#include "strtok.h"
 
#include "appargs.h"
 
 
size_t AppArgs::s_minCnt = 0;
size_t AppArgs::s_maxCnt = APPARGS_NOLIMIT;
 
AA_LONG_MAP AppArgs::s_longMap;
AA_SHORT_MAP AppArgs::s_shortMap;
AA_VAL_LIST AppArgs::s_valList;
 
 
void AppArgs::Init(size_t valMinCnt, size_t valMaxCnt)
{
s_minCnt = valMinCnt;
s_maxCnt = valMaxCnt;
}
 
void AppArgs::Destroy()
{
AA_LONG_MAP::iterator it = s_longMap.begin();
while (it != s_longMap.end()) {
delete (*it).second;
it++;
}
s_longMap.clear();
s_shortMap.clear();
s_valList.clear();
}
 
void AppArgs::Parse(int argc, const char* const* argv)
{
for (int i=1; i<argc; i++) {
// this shouldn't occur
if (argv[i] == NULL)
return;
if (IsOption(argv[i])) {
if (IsLongOption(argv[i])) {
std::string name, val;
bool hasVal;
if (ParseLong(argv[i], name, val, hasVal)) {
AA_LONG_MAP::iterator it = s_longMap.find(name);
if (it != s_longMap.end()) {
AppArgOption_t* pOpt = (*it).second;
pOpt->found = true;
pOpt->hasVal = hasVal;
pOpt->val = val;
}
}
}
else {
char name;
std::string val;
bool hasVal;
ParseShort(argv[i], name, val, hasVal);
AA_SHORT_MAP::iterator it = s_shortMap.find(name);
if (it != s_shortMap.end()) {
AppArgOption_t* pOpt = (*it).second;
pOpt->found = true;
if (hasVal) {
pOpt->hasVal = true;
pOpt->val = val;
}
else {
if (i+1 < argc && !IsOption(argv[i+1])) {
pOpt->hasVal = true;
pOpt->val = argv[i+1];
i++;
}
else {
pOpt->hasVal = false;
}
}
}
}
}
else {
s_valList.push_back(argv[i]);
}
}
}
 
bool AppArgs::IsValid()
{
size_t size = s_valList.size();
if (size < s_minCnt || size > s_maxCnt)
return false;
AA_LONG_MAP::iterator it = s_longMap.begin();
while (it != s_longMap.end()) {
AppArgOption_t* pOpt = (*it).second;
if (pOpt->mand && !(pOpt->found))
return false;
if (pOpt->type == AAT_MANDATORY_VALUE && pOpt->found && !(pOpt->hasVal))
return false;
it++;
}
return true;
}
 
bool AppArgs::ExistsOption(const std::string& rArg)
{
AA_LONG_MAP::iterator it = s_longMap.find(rArg);
return it != s_longMap.end() && (*it).second->found;
}
 
bool AppArgs::GetOption(const std::string& rArg, std::string& rVal)
{
AA_LONG_MAP::iterator it = s_longMap.find(rArg);
if (it == s_longMap.end())
return false;
AppArgOption_t* pOpt = (*it).second;
if (!(pOpt->found) || !(pOpt->hasVal))
return false;
rVal = pOpt->val;
return true;
}
 
bool AppArgs::AddOption(const std::string& rName, char cShort, AppArgType_t type, bool fMandatory)
{
if (s_longMap.find(rName) != s_longMap.end() || s_shortMap.find(cShort) != s_shortMap.end())
return false;
AppArgOption_t* pOpt = new AppArgOption_t();
pOpt->found = false;
pOpt->hasVal = false;
pOpt->mand = fMandatory;
pOpt->type = type;
s_longMap.insert(AA_LONG_MAP::value_type(rName, pOpt));
s_shortMap.insert(AA_SHORT_MAP::value_type(cShort, pOpt));
return true;
}
 
size_t AppArgs::GetValueCount()
{
return s_valList.size();
}
 
bool AppArgs::GetValue(size_t index, std::string& rVal)
{
if (index > s_valList.size())
return false;
rVal = s_valList[index];
return true;
}
 
void AppArgs::Dump()
{
fprintf(stderr, "Options:\n");
AA_LONG_MAP::iterator it = s_longMap.begin();
while (it != s_longMap.end()) {
AppArgOption_t* pOpt = (*it).second;
AA_SHORT_MAP::iterator it2 = s_shortMap.begin();
while (it2 != s_shortMap.end()) {
if ((*it2).second == pOpt) {
DumpOption((*it).first, (*it2).first, pOpt);
}
it2++;
}
it++;
}
fprintf(stderr, "Values:\n");
AA_VAL_LIST::iterator it3 = s_valList.begin();
while (it3 != s_valList.end()) {
fprintf(stderr, "%s\n", (*it3).c_str());
it3++;
}
}
 
 
 
bool AppArgs::IsOption(const char* pchStr)
{
if (strlen(pchStr) < 2)
return false;
return pchStr[0] == '-';
}
 
bool AppArgs::IsLongOption(const char* pchStr)
{
return pchStr[1] == '-';
}
 
bool AppArgs::ParseLong(const char* pchStr, std::string& rName, std::string& rVal, bool& rfHasVal)
{
StringTokenizer tok(pchStr+2, '=');
if (!tok.HasMoreTokens())
return false;
rName = tok.GetNextToken();
if (!tok.HasMoreTokens()) {
rfHasVal = false;
return true;
}
rVal = tok.GetRemainder();
rfHasVal = true;
return true;
}
 
void AppArgs::ParseShort(const char* pchStr, char& rcName, std::string& rVal, bool& rfHasVal)
{
rcName = pchStr[1];
size_t len = strlen(pchStr);
if (len == 2) {
rfHasVal = false;
return;
}
rVal = pchStr + 2;
rfHasVal = true;
}
 
void AppArgs::DumpOption(const std::string& rName, char cShort, AppArgOption_t* pOpt)
{
const char* s;
switch (pOpt->type) {
case AAT_NO_VALUE: s = "no value";
break;
case AAT_OPTIONAL_VALUE: s = "optional value";
break;
case AAT_MANDATORY_VALUE: s = "mandatory value";
break;
default:;
s = "unknown";
}
fprintf(stderr, "long='%s', short='%c', type='%s', found=%s, has_value=%s, value='%s'\n",
rName.c_str(),
cShort,
s,
pOpt->found ? "YES" : "NO",
pOpt->hasVal ? "YES" : "NO",
pOpt->val.c_str()
);
}