lsyncd/lsyncd.c

2837 lines
73 KiB
C
Raw Normal View History

/**
* lsyncd.c Live (Mirror) Syncing Demon
*
* License: GPLv2 (see COPYING) or any later version
*
* Authors: Axel Kittenberger <axel.kittenberger@univie.ac.at>
* Eugene Sanivsky <eugenesan@gmail.com>
*/
2008-08-06 13:04:01 +00:00
#include "config.h"
2008-10-06 20:35:52 +00:00
#define _GNU_SOURCE
2008-08-06 13:04:01 +00:00
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
2010-05-22 15:25:31 +00:00
#include <sys/times.h>
2008-08-06 13:04:01 +00:00
#ifdef HAVE_SYS_INOTIFY_H
# include <sys/inotify.h>
#else
# include "inotify-nosys.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
2008-08-06 13:04:01 +00:00
#include <errno.h>
#include <time.h>
#include <dirent.h>
#include <getopt.h>
2008-10-04 08:49:54 +00:00
#include <assert.h>
#include <syslog.h>
2008-08-06 13:04:01 +00:00
2008-11-20 21:38:58 +00:00
#ifdef XML_CONFIG
#include <libxml/parser.h>
#include <libxml/tree.h>
#endif
2010-08-04 14:20:28 +00:00
/**
* Define for debug memchecking.
*/
#define MEMCHECK
2010-08-04 14:20:28 +00:00
2010-08-02 17:11:56 +00:00
/**
* Number of inotifies to read max. at once from the kernel.
2010-08-02 17:11:56 +00:00
*/
#define INOTIFY_BUF_LEN (64 * (sizeof(struct inotify_event) + 16))
2008-08-06 13:04:01 +00:00
2010-08-02 17:11:56 +00:00
/**
* Initial size of vectors
*/
#define VECT_INIT_SIZE 2
/**
* Defaults values
*/
#define DEFAULT_BINARY "/usr/bin/rsync"
#define DEFAULT_CONF_FILENAME "/etc/lsyncd.conf.xml"
2010-08-05 11:22:12 +00:00
const uint32_t standard_event_mask =
IN_ATTRIB | IN_CLOSE_WRITE | IN_CREATE |
IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM |
IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR;
2010-05-24 12:32:34 +00:00
/**
* Macros to compare times() values
* (borrowed from linux/jiffies.h)
*
* time_after(a,b) returns true if the time a is after time b.
*/
#define time_after(a,b) ((long)(b) - (long)(a) < 0)
#define time_before(a,b) time_after(b,a)
#define time_after_eq(a,b) ((long)(a) - (long)(b) >= 0)
#define time_before_eq(a,b) time_after_eq(b,a)
/**
* Importance of log messages
*/
enum log_code {
DEBUG = 1,
NORMAL = 2,
ERROR = 3,
};
2008-08-06 13:04:01 +00:00
/**
* Possible exit codes for this application
2008-08-06 13:04:01 +00:00
*/
enum lsyncd_exit_code {
2008-11-20 21:38:58 +00:00
LSYNCD_SUCCESS = 0,
2008-12-05 23:54:13 +00:00
2008-11-20 21:38:58 +00:00
/* out-of memory */
LSYNCD_OUTOFMEMORY = 1,
2008-12-05 23:54:13 +00:00
2008-11-20 21:38:58 +00:00
/* file was not found, or failed to write */
LSYNCD_FILENOTFOUND = 2,
2008-08-06 13:04:01 +00:00
2008-11-20 21:38:58 +00:00
/* execution somehow failed */
LSYNCD_EXECFAIL = 3,
2008-08-06 13:04:01 +00:00
2008-11-20 21:38:58 +00:00
/* command-line arguments given to lsyncd are bad */
LSYNCD_BADPARAMETERS = 4,
2008-12-05 23:54:13 +00:00
2008-11-20 21:38:58 +00:00
/* Too many excludes files were specified */
LSYNCD_TOOMANYDIRECTORYEXCLUDES = 5,
2008-08-06 13:04:01 +00:00
2008-11-20 21:38:58 +00:00
/* something wrong with the config file */
LSYNCD_BADCONFIGFILE = 6,
2008-08-06 13:04:01 +00:00
/* cannot open inotify instance */
LSYNCD_NOINOTIFY = 7,
/* something internal went really wrong */
2008-11-20 21:38:58 +00:00
LSYNCD_INTERNALFAIL = 255,
};
2008-08-06 13:04:01 +00:00
/**
2008-11-20 21:38:58 +00:00
* An option paramater for the action call can either be:
2008-08-06 13:04:01 +00:00
*/
2008-11-20 21:38:58 +00:00
enum call_option_kind {
CO_EOL, // end of list,
CO_TEXT, // specified by text,
CO_EXCLUDE, // be the exclude file
CO_SOURCE, // be the source of the operation
CO_DEST, // be the destination of the operation
};
2008-08-06 13:04:01 +00:00
2008-11-20 21:38:58 +00:00
/*--------------------------------------------------------------------------*
* Structure definitions
*--------------------------------------------------------------------------*/
2008-08-06 13:04:01 +00:00
/**
2008-11-20 21:38:58 +00:00
* An option parameter for the call.
2008-08-06 13:04:01 +00:00
*/
2008-11-20 21:38:58 +00:00
struct call_option {
/**
* The kind of this option.
*/
enum call_option_kind kind;
/**
* The text if its text.
*/
char *text;
};
/**
2008-11-20 21:38:58 +00:00
* A configurated directory to sync (including subdirectories)
*
* In case of beeing called with simple argument without a config file
* there will be exactly one sync_directory.
*/
2008-11-20 21:38:58 +00:00
struct dir_conf {
/**
* Source dir to watch (no default value)
*/
char * source;
/**
2008-12-05 22:27:03 +00:00
* NULL terminated list of targets to rsync to (no default value).
2008-11-20 21:38:58 +00:00
*/
2008-12-05 22:27:03 +00:00
char ** targets;
2008-11-20 21:38:58 +00:00
/**
* binary to call (defaults to global default setting)
*/
char * binary;
/**
* the options to call the binary (defaults to global default setting)
*/
struct call_option * callopts;
2010-08-05 11:22:12 +00:00
/**
* bitmask of inotify events to watch (defaults to global default setting)
*/
uint32_t event_mask;
2008-11-20 21:38:58 +00:00
/**
* the exclude-file to pass to rsync (defaults to global default setting)
* TODO, Currently ignored!
*/
char * exclude_file;
};
/**
* Structure to store the directory watches.
*/
2010-08-03 18:02:46 +00:00
struct watch {
2008-08-12 11:51:46 +00:00
/**
* The watch descriptor returned by kernel.
*/
2008-08-12 11:51:46 +00:00
int wd;
/**
* The name of the directory.
* In case of the root dir to be watched, it is a full path
* and parent == -1. Otherwise its just the name of the
* directory and parent points to the parent directory thats
* also watched.
*/
2008-08-12 11:51:46 +00:00
char * dirname;
/**
2010-08-04 10:22:32 +00:00
* Points to the parent. NULL if no parent.
*/
2010-08-04 10:22:32 +00:00
struct watch *parent;
2008-08-06 13:04:01 +00:00
2010-05-22 15:25:31 +00:00
/**
* There is one or several delays to be handled
* dor this directory
*
* In case of non-atomic opperation this points
* directly to the delay struct.
*
* In case of atomic opperatoin this points to
* a file_delay_vector struct.
2010-05-22 15:25:31 +00:00
*/
void * dirdelay;
2010-05-22 15:25:31 +00:00
2008-11-20 21:38:58 +00:00
/**
* The applicable configuration for this directory
*/
struct dir_conf *dir_conf;
2008-11-20 21:38:58 +00:00
};
2008-08-06 13:04:01 +00:00
2008-11-20 21:38:58 +00:00
/**
* Global options relevant for logging.
* Part of struct global_options.
2008-11-20 21:38:58 +00:00
*/
struct log {
/**
* Global Option: The loglevel is how eloquent lsyncd will be.
*/
int loglevel;
2008-11-20 21:38:58 +00:00
/**
* Global Option: if true, do not detach and log to stdout/stderr.
*/
int flag_nodaemon;
/**
* If not NULL, this file will be accessed directly to write log messages to.
* If NULL, syslog will be used.
*
* Not as difference that the output of child processes (rsync) will be redirected
* to the logfile if specified, if syslogging the child-output will run into /dev/null.
*
* If flag_nodaemon is present stdout/stderr will be used.
*/
char * logfile;
};
2008-11-20 21:38:58 +00:00
2010-08-02 17:11:56 +00:00
/**
* Global variables
*/
struct global_options {
/**
* Options relevant for logging.
*/
struct log log;
/**
* Global Option, if true handle files atomicly instead of grouping changes up into a dir.
* by default call rsync with file filters.
*/
int flag_atomic;
2008-11-20 21:38:58 +00:00
/**
* Global Option: if true no action will actually be called.
*/
int flag_dryrun;
/**
* Global Option: if true, ignore rsync errors on startup.
* (during normal operations they have to be ignored eitherway,
* since rsync may also fail due e.g. the directory already
* beeing deleted when lsyncd wants to sync it.)
*/
int flag_stubborn;
/**
* Global Option: if true, lsyncd will not perform the startup sync.
*/
int flag_nostartup;
/**
* Global Option: pidfile, which holds the PID of the running daemon process.
*/
char *pidfile;
2008-11-20 21:38:58 +00:00
#ifdef XML_CONFIG
/**
* Global Option: the filename to read config from.
*/
char *conf_filename;
2008-11-20 21:38:58 +00:00
#endif
/**
* Global Option: this binary is used if no other specified in dir_conf.
*/
char *default_binary;
2010-08-05 11:22:12 +00:00
/**
* Global Option: default bitmask of inotify events to react upon.
*/
uint32_t default_event_mask;
2008-11-20 21:38:58 +00:00
/**
* Global Option: default exclude file
*/
char *default_exclude_file;
/**
* Global Option: default options to call the binary with.
*
* TODO copy on init.
*/
struct call_option *default_callopts;
2010-08-03 12:21:21 +00:00
/**
* Seconds of delay between event and action
*/
clock_t delay;
/**
* The configuratiton for dirs to synchronize
*/
struct dir_conf *dir_confs;
/**
* The number of configurated dirs to sync.
*/
int dir_conf_n;
};
2008-11-20 21:38:58 +00:00
/**
* Standard default options to call the binary with.
*/
struct call_option standard_callopts[] = {
{ CO_TEXT, "-lt%r" },
{ CO_TEXT, "--delete" },
{ CO_EXCLUDE, NULL },
{ CO_SOURCE, NULL },
{ CO_DEST, NULL },
{ CO_EOL, NULL },
};
/**
* Structure to store strings for the diversve inotfy masked events.
* Used for comfortable log messages only.
*/
struct inotify_mask_text {
/**
* the bit
*/
int mask;
/**
* and its meaning
*/
char const * text;
};
2008-08-06 13:04:01 +00:00
/**
* A constant that assigns every inotify mask a printable string.
* Used for debugging.
*/
struct inotify_mask_text mask_texts[] = {
2008-08-12 11:51:46 +00:00
{ IN_ACCESS, "ACCESS" },
{ IN_ATTRIB, "ATTRIB" },
{ IN_CLOSE_WRITE, "CLOSE_WRITE" },
{ IN_CLOSE_NOWRITE, "CLOSE_NOWRITE" },
{ IN_CREATE, "CREATE" },
{ IN_DELETE, "DELETE" },
{ IN_DELETE_SELF, "DELETE_SELF" },
{ IN_IGNORED, "IGNORED" },
2008-08-12 11:51:46 +00:00
{ IN_MODIFY, "MODIFY" },
{ IN_MOVE_SELF, "MOVE_SELF" },
{ IN_MOVED_FROM, "MOVED_FROM" },
{ IN_MOVED_TO, "MOVED_TO" },
{ IN_OPEN, "OPEN" },
{ 0, "" },
};
2008-08-06 13:04:01 +00:00
/**
* Holds all directories being watched.
2008-08-06 13:04:01 +00:00
*/
2010-08-03 18:02:46 +00:00
struct watch_vector {
2010-08-04 09:51:59 +00:00
/**
* list of pointers to all watches
2010-08-04 09:51:59 +00:00
*/
struct watch **data;
/**
* number of entries allocated
*/
size_t size;
/**
* number of entries used
*/
size_t len;
};
2010-08-06 13:35:56 +00:00
/**
* A delayed entry
*/
struct delay {
/**
* Pointer the watch.
2010-08-06 13:35:56 +00:00
*/
struct watch *watch;
/**
* Alarm time for the delay.
2010-08-06 13:35:56 +00:00
*/
clock_t alarm;
/**
* Pointer to the next delay.
*/
struct delay * next;
/**
* Pointer to the before delay.
*/
struct delay * before;
2010-08-06 13:35:56 +00:00
};
2010-08-04 10:52:31 +00:00
/**
* Holds all entries on delay.
*/
struct delay_vector {
/**
* pointer to first delay
2010-08-04 10:52:31 +00:00
*/
struct delay *first;
/**
* pointer to last delay
*/
struct delay *last;
2010-08-04 10:52:31 +00:00
};
2008-08-06 13:04:01 +00:00
/**
2008-08-12 11:51:46 +00:00
* Array of strings of directory names to include.
* This is limited to MAX_EXCLUDES.
2008-08-06 13:04:01 +00:00
* It's not worth to code a dynamic size handling...
*/
#define MAX_EXCLUDES 256
2010-08-04 20:22:38 +00:00
struct exclude_vector {
char * data[MAX_EXCLUDES];
size_t len;
};
2008-08-06 13:04:01 +00:00
2010-08-04 14:20:28 +00:00
/*--------------------------------------------------------------------------*
* MEMCHECK
*--------------------------------------------------------------------------*/
/**
2010-08-04 14:20:28 +00:00
* This routines keep track which memory allocs
* have not been freed. Debugging purposes.
*/
2010-08-04 14:20:28 +00:00
#ifdef MEMCHECK
#include <search.h>
/**
* Counts the number of s_[m|c]alloc's.
*/
2010-08-04 14:20:28 +00:00
int memc = 0;
/**
* A binary tree administered by the clib to store the
* pointers with a short description.
*/
2010-08-04 14:20:28 +00:00
void * mroot = NULL;
/**
* An entry to that tree
*/
2010-08-04 14:20:28 +00:00
struct mentry {
const void *data;
const char *desc;
};
/**
* Compares two pointers simply by their address
*/
2010-08-04 14:20:28 +00:00
int mcompare(const void *pa, const void *pb) {
const struct mentry *ma = (const struct mentry *) pa;
const struct mentry *mb = (const struct mentry *) pb;
if (ma->data < mb->data) {
return -1;
}
if (ma->data > mb->data) {
return 1;
}
return 0;
}
/**
* Prints nonfreed memory usage on exit/HUP
*/
2010-08-04 14:20:28 +00:00
void maction(const void *nodep, const VISIT which, const int depth) {
if (which == leaf || which == postorder) {
struct mentry * r = *((struct mentry **) nodep);
memc--;
fprintf(stderr, "<*> unfreed data %p:%s\n", r->data, r->desc);
}
}
#endif
2008-11-20 21:38:58 +00:00
/*--------------------------------------------------------------------------*
* Small generic helper routines.
* (signal catching, memory fetching, message output)
*--------------------------------------------------------------------------*/
/**
2010-08-04 20:22:38 +00:00
* Set to 0 in signal handler, when lsyncd should end ASAP.
* This can be either a TERM or a HUP signal.
* In case of HUP killed is 0 and start over.
2008-11-20 21:38:58 +00:00
*/
2008-08-06 13:04:01 +00:00
volatile sig_atomic_t keep_going = 1;
2010-08-04 20:22:38 +00:00
/**
* Received a TERM signal, TERMinate nicely.
*/
volatile sig_atomic_t termed = 0;
2008-11-20 21:38:58 +00:00
/**
* Called (out-of-order) when signals arrive
*/
2010-08-03 13:14:37 +00:00
void
catch_alarm(int sig)
2008-08-12 11:51:46 +00:00
{
2010-08-04 20:22:38 +00:00
switch(sig) {
case SIGTERM :
termed = 1;
/* fall through */
case SIGHUP :
keep_going = 0;
}
2008-08-06 13:04:01 +00:00
}
/**
* Just like exit, but logs the exit.
* Does not return!
*/
2010-08-03 13:14:37 +00:00
void
terminate(const struct log *log, int status)
{
if (log && !log->flag_nodaemon) {
if (log->logfile) {
FILE * flog;
flog = fopen(log->logfile, "a");
if (flog) {
fprintf(flog, "exit!");
fclose(flog);
}
} else {
syslog(LOG_ERR, "exit!");
}
}
exit(status);
}
/**
* Prints a message to either the log stream, preceding a timestamp or
* forwards a message to syslogd.
*
2008-08-06 13:04:01 +00:00
* Otherwise it behaves like printf();
*
* It will also always produce error messages on stderr.
* So when startup fails, the message will be logged
* _and_ displayed on screen. If lsyncd daemonized already,
* stderr will be run into the void of /dev/null.
2008-08-06 13:04:01 +00:00
*/
// activates gcc's printf warnings.
void
printlogf(const struct log *log,
int level,
const char *fmt, ...)
__attribute__((format(printf, 3, 4)));
2010-08-03 13:14:37 +00:00
void
printlogf(const struct log *log,
int level,
const char *fmt, ...)
2008-08-12 11:51:46 +00:00
{
va_list ap;
char * ct;
time_t mtime;
FILE * flog1 = NULL, * flog2 = NULL;
int sysp = 0;
2008-08-12 11:51:46 +00:00
if (log && level < log->loglevel) {
2008-08-12 11:51:46 +00:00
return;
}
if (log && !log->flag_nodaemon && log->logfile) {
flog1 = fopen(log->logfile, "a");
2008-08-12 11:51:46 +00:00
if (flog1 == NULL) {
fprintf(stderr, "cannot open logfile [%s]!\n", log->logfile);
terminate(log, LSYNCD_FILENOTFOUND);
2008-08-12 11:51:46 +00:00
}
}
time(&mtime);
ct = ctime(&mtime);
ct[strlen(ct) - 1] = 0; // cut trailing linefeed
2008-08-12 11:51:46 +00:00
switch (level) {
case DEBUG :
sysp = LOG_DEBUG;
if (!log || log->flag_nodaemon) {
flog2 = stdout;
}
2008-08-12 11:51:46 +00:00
break;
case NORMAL :
sysp = LOG_NOTICE;
if (!log || log->flag_nodaemon) {
flog2 = stdout;
}
2008-08-12 11:51:46 +00:00
break;
case ERROR :
sysp = LOG_ERR;
// write on stderr even when daemon.
flog2 = stderr;
2008-08-12 11:51:46 +00:00
break;
}
// write time on fileoutput
if (flog1) {
fprintf(flog1, "%s: ", ct);
}
if (level == ERROR) {
if (flog1) {
fprintf(flog1, "ERROR: ");
}
if (flog2) {
fprintf(flog2, "ERROR: ");
}
}
if (flog1) {
2010-07-09 06:40:53 +00:00
va_start(ap, fmt);
vfprintf(flog1, fmt, ap);
2010-07-09 06:40:53 +00:00
va_end(ap);
} else {
2010-07-09 06:40:53 +00:00
va_start(ap, fmt);
vsyslog(sysp, fmt, ap);
2010-07-09 06:40:53 +00:00
va_end(ap);
}
if (flog2) {
2010-07-09 06:40:53 +00:00
va_start(ap, fmt);
vfprintf(flog2, fmt, ap);
2010-07-09 06:40:53 +00:00
va_end(ap);
}
2008-08-12 11:51:46 +00:00
if (flog1) {
fprintf(flog1, "\n");
}
if (flog2) {
fprintf(flog2, "\n");
}
2008-08-12 11:51:46 +00:00
if (flog1) {
fclose(flog1);
2008-08-12 11:51:46 +00:00
}
}
2008-08-06 13:04:01 +00:00
/**
2008-08-12 11:51:46 +00:00
* "secured" malloc, meaning the deamon shall kill itself
* in case of out of memory.
2008-08-06 13:04:01 +00:00
*
2008-08-12 11:51:46 +00:00
* On linux systems, which is actually the only system this
2008-08-06 13:04:01 +00:00
* deamon will run at, due to the use of inotify, this is
2008-08-12 11:51:46 +00:00
* an "academic" cleaness only, linux will never return out
* memory, but kill a process to ensure memory will be
2008-08-06 13:04:01 +00:00
* available.
*/
2010-08-03 13:14:37 +00:00
void *
2010-08-04 14:20:28 +00:00
s_malloc(const struct log *log, size_t size, const char *desc)
2008-08-12 11:51:46 +00:00
{
void *r = malloc(size);
if (r == NULL) {
printlogf(log, ERROR, "Out of memory!");
terminate(log, LSYNCD_OUTOFMEMORY);
2008-08-12 11:51:46 +00:00
}
2010-08-04 14:20:28 +00:00
#ifdef MEMCHECK
{
struct mentry * mentry;
memc++;
mentry = malloc(sizeof(struct mentry));
mentry->data = r;
mentry->desc = desc;
if (!mentry) {
printlogf(log, ERROR, "Out of memory in memcheck!");
terminate(log, LSYNCD_OUTOFMEMORY);
}
tsearch(mentry, &mroot, mcompare);
}
#endif
2008-08-12 11:51:46 +00:00
return r;
2008-08-06 13:04:01 +00:00
}
/**
* "secured" calloc.
*/
2010-08-03 13:14:37 +00:00
void *
2010-08-04 14:20:28 +00:00
s_calloc(const struct log *log, size_t nmemb, size_t size, const char *desc)
2008-08-12 11:51:46 +00:00
{
void *r = calloc(nmemb, size);
if (r == NULL) {
printlogf(log, ERROR, "Out of memory!");
terminate(log, LSYNCD_OUTOFMEMORY);
2008-08-12 11:51:46 +00:00
}
2010-08-04 14:20:28 +00:00
#ifdef MEMCHECK
{
struct mentry * mentry;
memc++;
mentry = malloc(sizeof(struct mentry));
mentry->data = r;
mentry->desc = desc;
if (!mentry) {
printlogf(log, ERROR, "Out of memory in memcheck!");
terminate(log, LSYNCD_OUTOFMEMORY);
}
tsearch(mentry, &mroot, mcompare);
}
#endif
2008-08-12 11:51:46 +00:00
return r;
2008-08-06 13:04:01 +00:00
}
/**
* "secured" realloc.
*/
2010-08-03 13:14:37 +00:00
void *
s_realloc(const struct log *log, void *ptr, size_t size)
2008-08-12 11:51:46 +00:00
{
void *r = realloc(ptr, size);
if (r == NULL) {
printlogf(log, ERROR, "Out of memory!");
terminate(log, LSYNCD_OUTOFMEMORY);
2008-08-12 11:51:46 +00:00
}
2010-08-04 14:20:28 +00:00
#ifdef MEMCHECK
{
struct mentry * mentry = malloc(sizeof(struct mentry));
struct mentry **ret;
if (!mentry) {
printlogf(log, ERROR, "Out of memory in memcheck!");
terminate(log, LSYNCD_OUTOFMEMORY);
}
if (ptr == NULL) {
fprintf(stderr, "<*> Reallocating NULL!?\n");
return r;
}
// first delete the old entry
mentry->data = ptr;
ret = tfind(mentry, &mroot, mcompare);
if (ret == NULL) {
fprintf(stderr, "<*> Memcheck error, reallocating unknown pointer %p!\n", ptr);
return r;
}
mentry->desc = (*ret)->desc;
tdelete(mentry, &mroot, mcompare);
// and reenter the reallocated entry
mentry->data = r;
tsearch(mentry, &mroot, mcompare);
}
#endif
2008-08-12 11:51:46 +00:00
return r;
2008-08-06 13:04:01 +00:00
}
2008-10-06 20:35:52 +00:00
/**
* "secured" strdup.
*/
2010-08-03 13:14:37 +00:00
char *
2010-08-04 14:20:28 +00:00
s_strdup(const struct log *log, const char *src, const char *desc)
2008-10-06 20:35:52 +00:00
{
char *s = strdup(src);
if (s == NULL) {
printlogf(log, ERROR, "Out of memory!");
terminate(log, LSYNCD_OUTOFMEMORY);
2008-10-06 20:35:52 +00:00
}
2010-08-04 14:20:28 +00:00
#ifdef MEMCHECK
{
struct mentry * mentry;
memc++;
mentry = malloc(sizeof(struct mentry));
mentry->data = s;
mentry->desc = desc;
if (!mentry) {
printlogf(log, ERROR, "Out of memory in memcheck!");
terminate(log, LSYNCD_OUTOFMEMORY);
}
tsearch(mentry, &mroot, mcompare);
}
#endif
2008-10-06 20:35:52 +00:00
return s;
}
2010-08-04 14:20:28 +00:00
#ifdef MEMCHECK
/**
* Only needed when memory usage checking.
* Removes the entry of the freed memory from the tracking tree.
*/
2010-08-04 14:20:28 +00:00
void
s_free(void *p) {
struct mentry mentry = {0,};
struct mentry **r;
memc--;
if (p == NULL) {
fprintf(stderr, "<*> Memcheck freeing NULL!\n");
return;
}
mentry.data = p;
r = tdelete(&mentry, &mroot, mcompare);
if (r == NULL) {
fprintf(stderr, "<*> Memcheck error, freeing unknown pointer %p!\n", p);
}
free(p);
}
#else
#define s_free(x) free(x)
2010-08-04 14:20:28 +00:00
#endif
2008-10-06 20:35:52 +00:00
/**
2010-08-03 13:14:37 +00:00
* Returns the canonicalized path of a directory with a final '/'.
2008-10-06 20:35:52 +00:00
* Makes sure it is a directory.
*/
2010-08-03 13:14:37 +00:00
char *
realdir(const struct log *log, const char *dir)
2008-10-06 20:35:52 +00:00
{
2010-08-04 14:20:28 +00:00
char* cs = s_malloc(log, PATH_MAX+1, "realdir/cs");
2008-10-07 06:30:40 +00:00
cs = realpath(dir, cs);
2008-10-06 20:35:52 +00:00
if (cs == NULL) {
return NULL;
}
if (strlen(cs) + 1 >= PATH_MAX) {
2008-10-06 20:35:52 +00:00
// at systems maxpath already, we cannot add a '/' anyway.
return NULL;
}
struct stat st;
stat(cs, &st);
if (!S_ISDIR(st.st_mode)) {
2010-08-04 14:20:28 +00:00
s_free(cs);
2008-10-06 20:35:52 +00:00
return NULL;
}
2008-10-06 20:40:28 +00:00
strcat(cs, "/");
2008-10-06 20:35:52 +00:00
return cs;
}
2010-08-04 14:20:28 +00:00
/*--------------------------------------------------------------------------*
* Options.
*--------------------------------------------------------------------------*/
2010-08-04 18:15:55 +00:00
/**
* Cleans up the memory used by a CO_EOL terminated array of call options
*
* @param call_options the array to free.
*/
void
free_options(struct call_option *options) {
struct call_option *co = options;
while (co->kind != CO_EOL) {
if (co->text) {
s_free(co->text);
}
co++;
}
s_free(options);
}
2010-08-04 14:20:28 +00:00
/**
* (Re)sets global options to default values.
*/
void
reset_options(struct global_options *opts) {
opts->log.loglevel = NORMAL;
opts->log.flag_nodaemon = 0;
if (opts->log.logfile) {
s_free(opts->log.logfile);
opts->log.logfile = NULL;
}
opts->flag_atomic = 0;
2010-08-04 14:20:28 +00:00
opts->flag_dryrun = 0;
opts->flag_stubborn = 0;
opts->flag_nostartup = 0;
if (opts->pidfile) {
s_free(opts->pidfile);
opts->pidfile = NULL;
}
#ifdef XML_CONFIG
if (opts->conf_filename && opts->conf_filename != DEFAULT_CONF_FILENAME) {
s_free(opts->conf_filename);
}
opts->conf_filename = DEFAULT_CONF_FILENAME;
#endif
if (opts->default_binary && opts->default_binary != DEFAULT_BINARY) {
s_free(opts->default_binary);
}
opts->default_binary = DEFAULT_BINARY;
2010-08-05 11:22:12 +00:00
opts->default_event_mask = standard_event_mask;
2010-08-04 14:20:28 +00:00
if (opts->default_exclude_file) {
s_free(opts->default_exclude_file);
opts->default_exclude_file = NULL;
}
2010-08-04 18:15:55 +00:00
if (opts->default_callopts != standard_callopts) {
if (opts->default_callopts) {
free_options(opts->default_callopts);
}
opts->default_callopts = standard_callopts;
}
2010-08-04 14:20:28 +00:00
opts->delay = 5;
2010-08-04 18:15:55 +00:00
if (opts->dir_confs) {
int i;
for(i = 0; i < opts->dir_conf_n; i++) {
struct dir_conf *dc = opts->dir_confs + i;
if (dc->source) {
s_free(dc->source);
}
{
char **t = dc->targets;
while (*t) {
s_free(*t);
t++;
}
s_free(dc->targets);
}
if (dc->binary) {
s_free(dc->binary);
}
if (dc->callopts) {
free_options(dc->callopts);
dc->callopts = NULL;
}
if (dc->exclude_file) {
s_free(dc->exclude_file);
}
}
s_free(opts->dir_confs);
opts->dir_confs = NULL;
}
2010-08-04 14:20:28 +00:00
opts->dir_conf_n = 0;
};
2008-11-20 21:38:58 +00:00
/*--------------------------------------------------------------------------*
2010-08-03 13:14:37 +00:00
* Per directory configuration handling.
2008-11-20 21:38:58 +00:00
*--------------------------------------------------------------------------*/
/**
* (re)allocates space for a new dir_config and sets all values to 0/Null.
*
* (Yes we know, its a bit unoptimal, since when 6 dir_confs are given
* in the config file, lsyncd will reallocate dir_confs 6 times. Well we
* can live with that.)
*
* @return the pointer to the newly allocated dir_conf
*/
2010-08-03 13:14:37 +00:00
struct dir_conf *
new_dir_conf(struct global_options *opts) {
const struct log* log = &opts->log;
if (opts->dir_conf_n > 0) {
// enhance allocated space by 1.
opts->dir_conf_n++;
opts->dir_confs = s_realloc(log, opts->dir_confs, opts->dir_conf_n * sizeof(struct dir_conf));
memset(opts->dir_confs + opts->dir_conf_n - 1, 0, sizeof(struct dir_conf));
// creates targets NULL terminator (no targets yet)
2010-08-04 14:20:28 +00:00
opts->dir_confs[opts->dir_conf_n - 1].targets = s_calloc(log, 1, sizeof(char *), "dir_conf");
return opts->dir_confs + opts->dir_conf_n - 1;
} else {
// create the memory.
opts->dir_conf_n = 1;
2010-08-04 18:15:55 +00:00
opts->dir_confs = s_calloc(log, opts->dir_conf_n, sizeof(struct dir_conf), "dir_confs");
2008-12-05 22:27:03 +00:00
// creates targets NULL terminator (no targets yet)
2010-08-04 14:20:28 +00:00
opts->dir_confs[0].targets = s_calloc(log, 1, sizeof(char *), "dir_conf-target");
return opts->dir_confs;
}
2008-11-20 21:38:58 +00:00
}
2008-12-05 22:27:03 +00:00
/**
* Adds a target to a dir_conf.target.
* *target string will duped.
2008-12-05 22:27:03 +00:00
*
* @param dir_conf dir_conf to add the target to.
* @param target target to add.
*/
2010-08-03 13:14:37 +00:00
void
dir_conf_add_target(const struct log *log, struct dir_conf *dir_conf, char *target)
2008-12-05 22:27:03 +00:00
{
char **t;
int target_n = 0;
2010-07-27 13:23:17 +00:00
// count current targets
2008-12-05 22:27:03 +00:00
for (t = dir_conf->targets; *t; ++t) {
target_n++;
}
dir_conf->targets = s_realloc(log, dir_conf->targets, (target_n + 2) * sizeof(char *));
2010-08-04 14:20:28 +00:00
dir_conf->targets[target_n] = s_strdup(log, target, "dupped target");
2008-12-05 22:27:03 +00:00
dir_conf->targets[target_n + 1] = NULL;
}
2010-05-22 15:25:31 +00:00
/*--------------------------------------------------------------------------*
* Tackle list handling.
*--------------------------------------------------------------------------*/
/**
* Adds a directory on the delays list
2010-05-22 15:25:31 +00:00
*
* @param opts global options
2010-08-04 11:57:35 +00:00
* @param delays the delays FIFO
* @param watch the index in watches to the directory
* @param alarm times() when the directory should be acted
2010-05-22 15:25:31 +00:00
*/
2010-08-03 13:14:37 +00:00
bool
append_delay(const struct global_options *opts,
2010-08-04 11:57:35 +00:00
struct delay_vector *delays,
2010-08-04 10:22:32 +00:00
struct watch *watch,
clock_t alarm)
{
const struct log *log = &opts->log;
struct delay * newd;
if (watch->dirdelay) {
2010-05-22 15:25:31 +00:00
return false;
}
newd = s_calloc(log, 1, sizeof(struct delay), "a delay");
newd->watch = watch;
newd->alarm = alarm;
newd->next = NULL;
if (opts->flag_atomic) {
exit(1); //TODO ATOMIC
} else {
watch->dirdelay = newd;
}
if (!delays->first) {
// delays vector was empty
delays->first = delays->last = newd;
} else {
delays->last->next = newd;
newd->before = delays->last;
delays->last = newd;
2010-08-04 10:22:32 +00:00
}
2010-05-22 15:25:31 +00:00
return true;
}
/**
* Removes the first entry on the delay list.
2010-08-04 11:57:35 +00:00
*
* @param delays the delay FIFO.
2010-05-22 15:25:31 +00:00
*/
void
remove_first_delay(const struct global_options *opts, struct delay_vector *delays)
2010-08-04 11:00:27 +00:00
{
struct delay *fd = delays->first;
if (opts->flag_atomic) {
exit(1); // TODO ATOMIC
} else {
fd->watch->dirdelay = NULL;
}
delays->first = fd->next;
if (delays->first) {
delays->first->before = NULL;
} else {
// this was the only entry
delays->last = NULL;
}
s_free(fd);
2010-05-22 15:25:31 +00:00
}
2008-11-20 21:38:58 +00:00
/*--------------------------------------------------------------------------*
* ToSync Stack handling.
*--------------------------------------------------------------------------*/
/**
* Parses an option text, replacing all '%' specifiers with
* elaborated stuff. duh, currently there is only one, so this
* fuction is a bit overkill but oh well :-)
*
* @param text string to parse.
* @param recursive info for replacements.
*
* @return a newly allocated string.
*/
2010-08-03 13:14:37 +00:00
char *
parse_option_text(const struct log *log, char *text, bool recursive)
2008-11-20 21:38:58 +00:00
{
2010-08-04 14:20:28 +00:00
char * str = s_strdup(log, text, "dupped option text");
2008-11-20 21:38:58 +00:00
char * chr; // search result for %.
// replace all '%' specifiers with there special meanings
for(chr = strchr(str, '%'); chr; chr = strchr(str, '%')) {
char *p;
// chr points now to the '%' thing to be replaced
switch (chr[1]) {
case 'r' : // replace %r with 'r' when recursive or 'd' when not.
chr[0] = recursive ? 'r' : 'd';
for(p = chr + 1; *p != 0; p++) {
p[0] = p[1];
}
break;
case 0: // wtf, '%' was at the end of the string!
default : // unknown char
printlogf(log, ERROR,
"don't know how to handle '%%%c' specifier in \"%s\"!", chr[1], text);
terminate(log, LSYNCD_BADPARAMETERS);
2008-11-20 21:38:58 +00:00
}
}
return str;
}
2010-07-12 07:03:01 +00:00
/**
2010-07-27 13:31:55 +00:00
* Creates one string with all arguments concated.
*
2010-07-12 07:03:01 +00:00
* @param argv the arguments
* @param argc number of arguments
*/
2010-08-03 13:14:37 +00:00
char *
get_arg_str(const struct log *log, char **argv, int argc) {
2010-07-12 07:03:01 +00:00
int i;
int len = 0;
char * str;
// calc length
for (i = 0; i < argc; i++) {
len += strlen(argv[i]);
}
// alloc
2010-08-04 14:20:28 +00:00
str = s_malloc(log, len + 2 * argc + 1, "argument string");
2010-07-12 07:03:01 +00:00
str[0] = 0;
for(i = 0; i < argc; i++) {
if (i > 0) {
strcat(str, ", ");
}
strcat(str, argv[i]);
}
return str;
}
2008-08-06 13:04:01 +00:00
/**
2008-11-20 21:38:58 +00:00
* Calls the specified action (most likely rsync) to sync from src to dest.
* Returns after the forked process has finished.
2008-08-06 13:04:01 +00:00
*
2010-08-04 11:22:32 +00:00
* @param dir_conf the config applicatable for this dir.
* @param src source string.
* @param dest destination string,
* @param recursive if true -r will be handled on, -d (single directory) otherwise
*
2010-08-04 11:22:32 +00:00
* @return true if successful, false if not.
2008-08-06 13:04:01 +00:00
*/
2010-08-03 13:14:37 +00:00
bool
action(const struct global_options *opts,
2010-08-04 11:22:32 +00:00
struct dir_conf *dir_conf,
const char *src,
const char *dest,
2010-08-03 13:14:37 +00:00
bool recursive)
2008-08-12 11:51:46 +00:00
{
pid_t pid;
int status;
const int MAX_ARGS = 100;
char * argv[MAX_ARGS];
2010-07-12 06:49:53 +00:00
int argc = 0;
int i;
2008-11-20 21:38:58 +00:00
struct call_option* optp;
const struct log* log = &opts->log;
2008-11-20 21:38:58 +00:00
optp = dir_conf->callopts ? dir_conf->callopts : opts->default_callopts;
// makes a copy of all call parameters
// step 1 binary itself
2010-08-04 14:20:28 +00:00
argv[argc++] = s_strdup(log, dir_conf->binary ? dir_conf->binary : opts->default_binary, "argv");
printlogf(log, NORMAL, " %s %s --> %s%s", argv[0], src, dest, recursive ? " (recursive)" : "");
// now all other parameters
2010-07-12 06:49:53 +00:00
for(; optp->kind != CO_EOL; optp++) {
2008-11-20 21:38:58 +00:00
switch (optp->kind) {
case CO_TEXT :
argv[argc++] = parse_option_text(log, optp->text, recursive);
2008-11-20 21:38:58 +00:00
continue;
case CO_EXCLUDE :
// --exclude-from and the exclude file
// insert only when the exclude file is present otherwise skip it.
if (dir_conf->exclude_file == NULL && opts->default_exclude_file == NULL) {
2008-11-20 21:38:58 +00:00
continue;
}
2010-08-04 14:20:28 +00:00
argv[argc++] = s_strdup(log, "--exclude-from", "argv exclude-from");
argv[argc++] = s_strdup(log, dir_conf->exclude_file ? dir_conf->exclude_file : opts->default_exclude_file, "argv exclude-file");
2008-11-20 21:38:58 +00:00
continue;
case CO_SOURCE :
2010-08-04 14:20:28 +00:00
argv[argc++] = s_strdup(log, src, "argv source");
2008-11-20 21:38:58 +00:00
continue;
case CO_DEST :
2010-08-04 14:20:28 +00:00
argv[argc++] = s_strdup(log, dest, "argv dest");
2008-11-20 21:38:58 +00:00
continue;
default:
2010-08-04 11:22:32 +00:00
printlogf(log, ERROR, "Internal error: unknown kind of option.");
terminate(log, LSYNCD_INTERNALFAIL);
2008-11-20 21:38:58 +00:00
}
if (argc >= MAX_ARGS) {
2010-08-04 11:22:32 +00:00
// check for error condition
printlogf(log, ERROR,
2010-08-04 11:22:32 +00:00
"Error: too many (>%i) options passed", argc);
2008-11-20 21:38:58 +00:00
return false;
}
}
argv[argc++] = NULL;
if (opts->flag_dryrun) {
2010-07-12 07:03:01 +00:00
// just make a nice log message
char * binary = dir_conf->binary ? dir_conf->binary : opts->default_binary;
char * argall = get_arg_str(log, argv, argc);
printlogf(log, NORMAL, "dry run: would call %s(%s)", binary, argall);
2010-08-04 14:20:28 +00:00
s_free(argall);
2010-07-12 07:03:01 +00:00
for (i = 0; i < argc; ++i) {
if (argv[i]) {
2010-08-04 14:20:28 +00:00
s_free(argv[i]);
2010-07-12 07:03:01 +00:00
}
}
2008-08-12 11:51:46 +00:00
return true;
}
pid = fork();
2008-08-06 13:04:01 +00:00
2008-08-12 11:51:46 +00:00
if (pid == 0) {
char * binary = dir_conf->binary ? dir_conf->binary : opts->default_binary;
if (!log->flag_nodaemon && log->logfile) {
if (!freopen(log->logfile, "a", stdout)) {
printlogf(log, ERROR, "cannot redirect stdout to [%s].", log->logfile);
}
if (!freopen(log->logfile, "a", stderr)) {
printlogf(log, ERROR, "cannot redirect stderr to [%s].", log->logfile);
}
2008-08-12 11:51:46 +00:00
}
2010-07-27 13:31:55 +00:00
execv(binary, argv);
// in a sane world execv does not return!
printlogf(log, ERROR, "Failed executing [%s]", binary);
terminate(log, LSYNCD_INTERNALFAIL);
2008-08-12 11:51:46 +00:00
}
2010-07-27 13:31:55 +00:00
// free the memory from the arguments.
2010-07-12 06:49:53 +00:00
for (i = 0; i < argc; ++i) {
2008-11-20 21:38:58 +00:00
if (argv[i]) {
2010-08-04 14:20:28 +00:00
s_free(argv[i]);
2008-11-20 21:38:58 +00:00
}
}
2008-08-12 11:51:46 +00:00
waitpid(pid, &status, 0);
2008-10-04 08:49:54 +00:00
assert(WIFEXITED(status));
2010-07-12 06:49:53 +00:00
if (WEXITSTATUS(status) == LSYNCD_INTERNALFAIL){
printlogf(log, ERROR,
2008-11-20 21:38:58 +00:00
"Fork exit code of %i, execv failure",
WEXITSTATUS(status));
return false;
} else if (WEXITSTATUS(status)) {
printlogf(log, NORMAL,
2008-11-20 21:38:58 +00:00
"Forked binary process returned non-zero return code: %i",
2008-12-05 23:54:13 +00:00
WEXITSTATUS(status));
return false;
2008-10-04 08:49:54 +00:00
}
2008-08-12 11:51:46 +00:00
printlogf(log, DEBUG, "Rsync of [%s] -> [%s] finished", src, dest);
2008-08-12 11:51:46 +00:00
return true;
}
2008-08-06 13:04:01 +00:00
/**
* Adds a directory to watch.
2008-08-06 13:04:01 +00:00
*
2010-08-05 11:22:12 +00:00
* @param opts global options
* @param watches the vector of watches
* @param inotify_fd inotify file descriptor
* @param pathname the absolute path of the directory to watch
* @param dirname the name of the directory only
2010-08-03 13:14:37 +00:00
* @param parent if not -1 the index to the parent directory that is already watched
* @param dir_conf the applicateable configuration
2008-08-06 13:04:01 +00:00
*
2010-08-04 10:22:32 +00:00
* @return the watches of the new dir, NULL on error
2008-08-06 13:04:01 +00:00
*/
2010-08-04 10:22:32 +00:00
struct watch *
2010-08-05 11:22:12 +00:00
add_watch(const struct global_options *opts,
struct watch_vector *watches,
2010-08-03 13:14:37 +00:00
int inotify_fd,
2010-08-04 10:22:32 +00:00
char const *pathname,
char const *dirname,
struct watch *parent,
struct dir_conf *dir_conf)
2008-08-12 11:51:46 +00:00
{
2010-08-05 11:22:12 +00:00
const struct log *log = &opts->log; // loginfo shortcut
int wd; // kernels inotify descriptor
int wi; // index to insert this watch into the watch vector
struct watch *w; // the new watch
2008-08-12 11:51:46 +00:00
2010-08-05 11:22:12 +00:00
wd = inotify_add_watch(inotify_fd, pathname,
dir_conf->event_mask ? dir_conf->event_mask : opts->default_event_mask);
2008-08-12 11:51:46 +00:00
if (wd == -1) {
printlogf(log, ERROR, "Cannot add watch %s (%d:%s)",
2008-11-20 21:38:58 +00:00
pathname, errno, strerror(errno));
2010-08-04 10:22:32 +00:00
return NULL;
2008-08-12 11:51:46 +00:00
}
// look if an unused slot can be found.
2010-08-04 09:51:59 +00:00
//
// lsyncd currently does not free unused slots, but marks
// them as unused with wd < 0.
2010-08-04 11:22:32 +00:00
for (wi = 0; wi < watches->len; wi++) {
if (watches->data[wi]->wd < 0) {
2008-08-12 11:51:46 +00:00
break;
}
}
2010-08-04 09:51:59 +00:00
// there is no unused entry
2010-08-04 11:22:32 +00:00
if (wi == watches->len) {
2010-08-04 09:51:59 +00:00
// extend the vector if necessary
2010-08-03 18:02:46 +00:00
if (watches->len + 1 >= watches->size) {
watches->size *= 2;
watches->data = s_realloc(log, watches->data,
2010-08-04 09:51:59 +00:00
watches->size * sizeof(struct watch *));
2008-08-12 11:51:46 +00:00
}
2010-08-04 09:51:59 +00:00
// allocate memory for a new watch
2010-08-04 14:20:28 +00:00
watches->data[watches->len++] = s_calloc(log, 1, sizeof(struct watch), "watch");
2008-08-12 11:51:46 +00:00
}
2010-08-04 11:22:32 +00:00
w = watches->data[wi];
w->wd = wd;
w->parent = parent;
2010-08-04 14:20:28 +00:00
w->dirname = s_strdup(log, dirname, "dirname");
2010-08-04 11:22:32 +00:00
w->dir_conf = dir_conf;
w->dirdelay = NULL;
2010-08-04 11:22:32 +00:00
return w;
2008-08-06 13:04:01 +00:00
}
/**
* Writes the path of a watched directory into pathname.
*
2010-08-04 10:22:32 +00:00
* @param pathname path to write to
* @param pathsize size of the pathname buffer
* @param watch watched dir to build path for
* @param prefix replace root dir with this (as target)
*
* @return -1 if pathname buffer was too small
* contents of pathname will be garbled then.
* strlen(pathname) if successful
*/
2010-08-03 13:14:37 +00:00
int
builddir(char *pathname,
int pathsize,
2010-08-04 10:22:32 +00:00
struct watch *watch,
char const *prefix)
{
int len = 0;
2010-08-04 10:22:32 +00:00
if (!watch) {
// TODO Is this ever called?
char const * p = prefix ? prefix : "";
len = strlen(p);
if (pathsize <= len) {
return -1;
}
strcpy(pathname, p);
2010-08-04 10:22:32 +00:00
} else if (!watch->parent) {
// this is a watch root.
2010-08-04 10:22:32 +00:00
char const * p = prefix ? prefix : watch->dirname;
len = strlen(p);
if (pathsize <= len) {
return -1;
}
strcpy(pathname, p);
} else {
// this is some sub dir
2010-08-04 11:00:27 +00:00
len = builddir(pathname, pathsize, watch->parent, prefix); /* recurse */
2010-08-04 10:22:32 +00:00
len += strlen(watch->dirname);
if (pathsize <= len) {
return -1;
}
2010-08-04 10:22:32 +00:00
strcat(pathname, watch->dirname);
}
// add the trailing slash if it is missing
if (*pathname && pathname[strlen(pathname)-1] != '/') {
strcat(pathname, "/");
len++;
}
return len;
}
2008-08-06 13:04:01 +00:00
/**
2010-08-04 11:00:27 +00:00
* Builds the abolute path name of a given directory watch
2008-08-06 13:04:01 +00:00
*
* @param pathname destination buffer to store the result to.
* @param pathsize max size of this buffer
2010-08-04 10:22:32 +00:00
* @param watch the watches of the directory.
* @param dirname if not NULL it is added at the end of pathname
* @param prefix if not NULL it is added at the beginning of pathname
2008-08-06 13:04:01 +00:00
*/
2010-08-03 13:14:37 +00:00
bool
buildpath(const struct log *log,
char *pathname,
int pathsize,
2010-08-04 10:22:32 +00:00
struct watch *watch,
2010-08-03 13:14:37 +00:00
const char *dirname,
const char *prefix)
2008-08-12 11:51:46 +00:00
{
2010-08-04 11:00:27 +00:00
int len = builddir(pathname, pathsize, watch, prefix);
if (len < 0) {
printlogf(log, ERROR, "path too long!");
return false;
2008-08-12 11:51:46 +00:00
}
if (dirname) {
if (pathsize < len + strlen(dirname) + 1) {
printlogf(log, ERROR, "path too long!");
2008-08-12 11:51:46 +00:00
return false;
}
strcat(pathname, dirname);
2008-08-12 11:51:46 +00:00
}
return true;
2008-08-06 13:04:01 +00:00
}
/**
* Syncs a directory.
2010-05-22 15:25:31 +00:00
* TODO: make better error handling (differ between
* directory gone away, and thus cannot work, or network
* failed)
*
2010-08-04 10:22:32 +00:00
* @param watch the watch of the directory.
2008-12-05 22:27:03 +00:00
*
* @returns true when all targets were successful.
*/
2010-08-03 13:14:37 +00:00
bool
rsync_dir(const struct global_options *opts,
struct watch *watch,
const char *event_text)
{
char pathname[PATH_MAX+1];
char destname[PATH_MAX+1];
2008-12-05 22:27:03 +00:00
bool status = true;
char ** target;
const struct log *log = &opts->log;
2010-08-04 11:00:27 +00:00
if (!buildpath(log, pathname, sizeof(pathname), watch, NULL, NULL)) {
return false;
}
printlogf(log, NORMAL, "%s: acting for %s.", event_text, pathname);
2010-08-04 10:22:32 +00:00
for (target = watch->dir_conf->targets; *target; target++) {
2010-08-04 11:00:27 +00:00
if (!buildpath(log, destname, sizeof(destname), watch, NULL, *target)) {
2008-12-05 22:27:03 +00:00
status = false;
continue;
}
// call the action to propagate changes in the directory
2010-08-04 10:22:32 +00:00
if (!action(opts, watch->dir_conf, pathname, destname, false)) {
printlogf(log, ERROR, "Action %s --> %s has failed.", pathname, destname);
2008-12-05 22:27:03 +00:00
status = false;
}
2008-11-20 21:38:58 +00:00
}
2008-12-05 22:27:03 +00:00
return status;
}
2010-05-22 15:25:31 +00:00
/**
* Puts a directory on the delay list OR
* directly calls rsync_dir if delay == 0;
2010-08-04 11:57:35 +00:00
*
* @param delays the delay FIFO
* @param delay if true will put it on the delay, if false act immediately
* @param watch the watches of the delayed directory.
* @param alarm times() when the directory handling should be fired.
* @param event_text event name for logging output
2010-05-22 15:25:31 +00:00
*/
2010-08-03 13:14:37 +00:00
void
2010-08-04 11:22:32 +00:00
delay_or_act_dir(const struct global_options *opts,
2010-08-04 11:57:35 +00:00
struct delay_vector *delays,
2010-08-04 11:22:32 +00:00
bool delay,
struct watch *watch,
clock_t alarm,
const char *event_text,
const char *event_name)
2010-05-22 15:25:31 +00:00
{
const struct log *log = &opts->log;
2010-08-04 11:22:32 +00:00
char pathname[PATH_MAX+1];
2010-05-22 15:25:31 +00:00
2010-08-04 11:22:32 +00:00
if (!delay) {
rsync_dir(opts, watch, event_text);
2010-05-22 15:25:31 +00:00
} else {
bool ret;
2010-08-04 11:22:32 +00:00
if (!buildpath(log, pathname, sizeof(pathname), watch, NULL, NULL)) {
return;
}
ret = append_delay(opts, delays, watch, alarm);
if (event_name) {
printlogf(log, NORMAL, "%s %s in %s%s - delayed.",
event_text, event_name, pathname, ret ? "" : " already");
2010-08-04 11:22:32 +00:00
} else {
printlogf(log, NORMAL, "%s in %si -%s delayed.",
event_text, pathname, ret ? "" : " already");
2010-08-04 11:22:32 +00:00
}
2010-05-22 15:25:31 +00:00
}
}
2010-08-05 11:22:12 +00:00
/**
* Looks up the inotify event mask for the specified event text.
*
* @param text the name of the event to look up
*
* @return the inotify event mask or 0 if the mask name is unknown.
*/
int
event_text_to_mask(char * text)
{
int mask = 0;
struct inotify_mask_text *p;
for (p = mask_texts; p->mask; p++) {
if (!strcmp(p->text, text)) {
mask = p->mask;
break;
}
}
return mask;
}
2008-08-06 13:04:01 +00:00
/**
2010-08-03 14:16:12 +00:00
* Adds a directory including all subdirectories to watch.
* And puts the directory with all subdirectories on the delay FIFO if act is true.
2008-08-06 13:04:01 +00:00
*
2010-08-03 14:16:12 +00:00
* @param opts global options
* @param watches the watch vector
2010-08-04 11:57:35 +00:00
* @param delays the delay vector
2010-08-04 20:22:38 +00:00
* @param excludes the excludes vector
2010-08-03 13:14:37 +00:00
* @param inotify_fd inotify file descriptor.
* @param dirname The name or absolute path of the directory to watch.
2010-08-04 11:57:35 +00:00
* @param parent If not NULL, the watches to the parent directory already watched.
* Must have absolute path if parent == NULL.
2010-08-04 11:22:32 +00:00
* @param dir_conf applicateable configuration
* @param act if true delay or act on the new directory (dont on startup)
*
2010-08-04 10:22:32 +00:00
* @returns the watches of the directory or NULL on fail.
2008-08-06 13:04:01 +00:00
*/
2010-08-04 10:22:32 +00:00
struct watch *
2010-08-03 14:16:12 +00:00
add_dirwatch(const struct global_options *opts,
struct watch_vector *watches,
2010-08-04 11:57:35 +00:00
struct delay_vector *delays,
2010-08-04 20:22:38 +00:00
struct exclude_vector *excludes,
2010-08-03 13:14:37 +00:00
int inotify_fd,
char const *dirname,
2010-08-04 10:22:32 +00:00
struct watch *parent,
struct dir_conf *dir_conf,
bool act)
2008-08-12 11:51:46 +00:00
{
2010-08-03 14:16:12 +00:00
const struct log *log = &opts->log;
2008-08-12 11:51:46 +00:00
DIR *d;
2010-08-04 11:22:32 +00:00
struct watch *w;
char pathname[PATH_MAX+1];
2010-08-04 11:00:27 +00:00
if (!buildpath(log, pathname, sizeof(pathname), parent, dirname, NULL)) {
2010-08-04 10:22:32 +00:00
return NULL;
2008-08-12 11:51:46 +00:00
}
2010-08-03 14:16:12 +00:00
{
int i;
2010-08-04 20:22:38 +00:00
for (i = 0; i < excludes->len; i++) {
if (!strcmp(pathname, excludes->data[i])) {
printlogf(log, NORMAL, "ignored %s because of exclusion.", pathname);
2010-08-04 10:22:32 +00:00
return NULL;
2010-08-03 14:16:12 +00:00
}
2008-08-12 11:51:46 +00:00
}
}
2010-08-03 14:16:12 +00:00
// watch this directory
2010-08-05 11:22:12 +00:00
w = add_watch(opts, watches, inotify_fd, pathname, dirname, parent, dir_conf);
2010-08-04 11:22:32 +00:00
if (!w) {
2010-08-04 10:22:32 +00:00
return NULL;
2008-08-12 11:51:46 +00:00
}
// if acting put this directory on list to be synced.
// time is now so it as soon as possible, but it will be on
// top of the delay FIFO, so the current directory is
// guaranteed to be synced first.
if (act) {
delay_or_act_dir(opts, delays, true, w, times(NULL), "new subdirectory", "");
}
2010-08-03 14:16:12 +00:00
2008-08-12 11:51:46 +00:00
if (strlen(pathname) + strlen(dirname) + 2 > sizeof(pathname)) {
printlogf(log, ERROR, "pathname too long %s//%s", pathname, dirname);
2010-08-04 10:22:32 +00:00
return NULL;
2008-08-12 11:51:46 +00:00
}
d = opendir(pathname);
if (d == NULL) {
printlogf(log, ERROR, "cannot open dir %s.", dirname);
2010-08-04 10:22:32 +00:00
return NULL;
2008-08-12 11:51:46 +00:00
}
2010-05-22 12:09:57 +00:00
while (keep_going) { // terminate early on KILL signal
2008-11-21 09:47:28 +00:00
bool isdir;
2010-08-03 14:16:12 +00:00
struct dirent *de = readdir(d);
2008-08-12 11:51:46 +00:00
2010-05-22 12:09:57 +00:00
if (de == NULL) { // finished reading the directory
break;
2008-08-12 11:51:46 +00:00
}
2010-05-22 12:09:57 +00:00
// detemine if an entry is a directory or file
2008-11-21 09:47:28 +00:00
if (de->d_type == DT_DIR) {
isdir = true;
} else if (de->d_type == DT_UNKNOWN) {
// in case of reiserfs, d_type will be UNKNOWN, how evil! :-(
// use traditional means to determine if its a directory.
2010-08-03 14:16:12 +00:00
char subdir[PATH_MAX+1];
struct stat st;
2010-08-04 11:22:32 +00:00
isdir = buildpath(log, subdir, sizeof(subdir), w, de->d_name, NULL) &&
2008-11-21 09:47:28 +00:00
!stat(subdir, &st) &&
2008-12-05 23:54:13 +00:00
S_ISDIR(st.st_mode);
2008-11-21 09:47:28 +00:00
} else {
isdir = false;
}
2010-05-22 12:09:57 +00:00
// add watches if its a directory and not . or ..
2008-11-21 09:47:28 +00:00
if (isdir && strcmp(de->d_name, "..") && strcmp(de->d_name, ".")) {
2010-08-04 10:22:32 +00:00
// recurse into subdirectories
printlogf(log, NORMAL, "%s %s%s",
act ? "encountered" : "watching",
pathname, de->d_name);
add_dirwatch(opts, watches, delays, excludes, inotify_fd, de->d_name, w, dir_conf, act);
}
2008-08-12 11:51:46 +00:00
}
closedir(d);
2010-08-04 11:22:32 +00:00
return w;
2008-08-06 13:04:01 +00:00
}
/**
* Removes a watched dir, including recursevily all subdirs.
*
2010-08-04 11:57:35 +00:00
* @param opts global options
* @param watches the watch vector
* @param delays the delay FIFO
* @param inotify_fd inotify file descriptor
* @param name optionally. If not NULL, the directory name
* to remove which is a child of parent
* @param parent the parent directory of the
* directory 'name' to remove, or to be removed
* itself if name == NULL.
2008-08-06 13:04:01 +00:00
*/
2010-08-03 14:16:12 +00:00
bool
2010-08-03 13:14:37 +00:00
remove_dirwatch(const struct global_options *opts,
struct watch_vector *watches,
2010-08-04 11:57:35 +00:00
struct delay_vector *delays,
2010-08-03 13:14:37 +00:00
int inotify_fd,
const char * name,
2010-08-04 10:22:32 +00:00
struct watch *parent)
2008-08-12 11:51:46 +00:00
{
2010-08-03 12:21:21 +00:00
const struct log *log = &opts->log;
2010-08-04 11:22:32 +00:00
struct watch *w; // the watch to remove
2008-08-12 11:51:46 +00:00
if (name) {
int i;
2008-08-12 11:51:46 +00:00
// look for the child with the name
2010-08-04 10:22:32 +00:00
// TODO optimize by using subdir lists
2010-08-03 18:02:46 +00:00
for (i = 0; i < watches->len; i++) {
2010-08-04 11:22:32 +00:00
w = watches->data[i];
if (w->wd >= 0 && w->parent == parent && !strcmp(name, w->dirname)) {
2008-08-12 11:51:46 +00:00
break;
}
}
2010-08-03 18:02:46 +00:00
if (i >= watches->len) {
printlogf(log, ERROR, "Cannot find entry for %s:/:%s :-(",
2010-08-04 10:22:32 +00:00
parent->dirname, name);
2008-08-12 11:51:46 +00:00
return false;
}
} else {
2010-08-04 11:22:32 +00:00
w = parent;
2008-08-12 11:51:46 +00:00
}
{
// recurse into all subdirectories removing them.
2010-08-04 10:22:32 +00:00
// TODO possible optimization by keeping a list of subdirs
int i;
2010-08-03 18:02:46 +00:00
for (i = 0; i < watches->len; i++) {
2010-08-04 11:22:32 +00:00
struct watch * iw = watches->data[i];
if (iw->wd >= 0 && iw->parent == w) {
// recurse into the subdirectory
2010-08-04 11:57:35 +00:00
remove_dirwatch(opts, watches, delays, inotify_fd, NULL, iw);
}
2008-08-12 11:51:46 +00:00
}
}
2010-08-04 11:22:32 +00:00
inotify_rm_watch(inotify_fd, w->wd);
2010-08-04 10:22:32 +00:00
// mark this entry invalid
2010-08-04 11:22:32 +00:00
w->wd = -1;
2010-08-04 14:20:28 +00:00
s_free(w->dirname);
2010-08-04 11:22:32 +00:00
w->dirname = NULL;
2008-08-12 11:51:46 +00:00
if (!w->dirdelay) {
return true;
}
// otherwise remove the delay entries for this dir.
if (opts->flag_atomic) {
exit(1); // TODO ATOMIC
} else {
struct delay * d = (struct delay *) w->dirdelay;
if (d->before) {
d->before->next = d->next;
} else {
// this was first entry
delays->first = d->next;
}
if (d->next) {
d->next->before = d->before;
} else {
// this was last entry
delays->last = d->before;
2010-05-22 16:21:42 +00:00
}
}
2008-08-12 11:51:46 +00:00
return true;
2008-08-06 13:04:01 +00:00
}
/**
2010-08-04 10:22:32 +00:00
* Find the matching watch descriptor
*
* @param watches the watch vector
* @param wd the wd (watch descriptor) given by inotify
*
2010-08-04 10:22:32 +00:00
* @return the watch or NULL if not found
*/
2010-08-04 10:22:32 +00:00
struct watch *
get_watch(const struct watch_vector *watches,
int wd)
{
int i;
2010-08-03 18:02:46 +00:00
for (i = 0; i < watches->len; i++) {
2010-08-04 09:51:59 +00:00
if (watches->data[i]->wd == wd) {
2010-08-04 10:22:32 +00:00
return watches->data[i];
}
}
2010-08-04 10:22:32 +00:00
return NULL;
}
2008-08-06 13:04:01 +00:00
/**
* Handles an inotify event.
*
2010-08-04 11:57:35 +00:00
* @param opts global options
* @param watches the watch vector
* @param delays the delay FIFO
* @param exlucdes the exclusions
2010-08-04 11:57:35 +00:00
* @param inotify_fd inotify file descriptor
* @param event the event to handle
* @param alarm times() moment when it should fire
2008-08-06 13:04:01 +00:00
*/
2010-08-03 13:14:37 +00:00
bool
handle_event(const struct global_options *opts,
struct watch_vector *watches,
2010-08-04 11:57:35 +00:00
struct delay_vector *delays,
2010-08-04 20:22:38 +00:00
struct exclude_vector *excludes,
2010-08-03 13:14:37 +00:00
int inotify_fd,
struct inotify_event *event,
clock_t alarm)
2008-08-12 11:51:46 +00:00
{
2010-08-04 11:22:32 +00:00
const struct log *log = &opts->log;
2008-08-12 11:51:46 +00:00
char masktext[255] = {0,};
2010-08-04 10:22:32 +00:00
struct watch *watch;
2008-08-12 11:51:46 +00:00
2010-08-04 11:22:32 +00:00
{
// creates a string for logging that shows which flags
// were raised in the event
struct inotify_mask_text *p;
int mask = event->mask;
for (p = mask_texts; p->mask; p++) {
if (mask & p->mask) {
if (strlen(masktext) + strlen(p->text) + 3 >= sizeof(masktext)) {
printlogf(log, ERROR, "bufferoverflow in handle_event");
return false;
}
if (*masktext) {
strcat(masktext, ", ");
}
strcat(masktext, p->text);
2008-08-12 11:51:46 +00:00
}
}
2010-08-04 11:22:32 +00:00
printlogf(log, DEBUG, "inotfy event: %s:%s", masktext, event->name);
2008-08-12 11:51:46 +00:00
}
if (IN_IGNORED & event->mask) {
return true;
}
2010-08-04 11:22:32 +00:00
{
// TODO, is this needed? or will it be excluded already?
int i;
2010-08-04 20:22:38 +00:00
for (i = 0; i < excludes->len; i++) {
if (!strcmp(event->name, excludes->data[i])) {
2010-08-04 11:22:32 +00:00
return true;
}
}
}
2008-08-12 11:51:46 +00:00
2010-08-04 10:22:32 +00:00
watch = get_watch(watches, event->wd);
if (!watch) {
2010-08-03 14:49:53 +00:00
// this can happen in case of data moving faster than lsyncd can monitor it.
printlogf(log, NORMAL,
"event %s:%s is not from a watched directory (which presumably was recently deleted)", masktext, event->name ? event->name : "");
2008-08-12 11:51:46 +00:00
return false;
}
2010-08-04 11:22:32 +00:00
// put the watch on the delay or act if delay == 0
2009-04-05 14:49:27 +00:00
if ((IN_ATTRIB | IN_CREATE | IN_CLOSE_WRITE | IN_DELETE |
2008-11-20 21:38:58 +00:00
IN_MOVED_TO | IN_MOVED_FROM) & event->mask
) {
delay_or_act_dir(opts, delays, opts->delay > 0, watch, alarm, masktext, event->name);
2008-11-20 21:38:58 +00:00
} else {
printlogf(log, DEBUG, "... ignored this event.");
2008-11-20 21:38:58 +00:00
}
2010-08-04 11:57:35 +00:00
// in case of a new directory create new watches of the subdir
if (((IN_CREATE | IN_MOVED_TO) & event->mask) && (IN_ISDIR & event->mask)) {
add_dirwatch(opts, watches, delays, excludes, inotify_fd, event->name, watch, watch->dir_conf, true);
2010-08-04 11:57:35 +00:00
}
// in case of a removed directory remove watches from the subdir
if (((IN_DELETE | IN_MOVED_FROM) & event->mask) && (IN_ISDIR & event->mask)) {
remove_dirwatch(opts, watches, delays, inotify_fd, event->name, watch);
}
2008-11-20 21:38:58 +00:00
return true;
2008-08-06 13:04:01 +00:00
}
/**
* The control loop waiting for inotify events
*
2010-08-03 13:14:37 +00:00
* @param opts global options
* @param watches the watch vector
2010-08-04 11:57:35 +00:00
* @param delays the delay vector
2010-08-04 20:22:38 +00:00
* @param excludes the exclude vector
2010-08-03 13:14:37 +00:00
* @param inotify_fd inotify file descriptor
2008-08-06 13:04:01 +00:00
*/
2010-08-03 13:14:37 +00:00
bool
master_loop(const struct global_options *opts,
struct watch_vector *watches,
2010-08-04 11:57:35 +00:00
struct delay_vector *delays,
2010-08-04 20:22:38 +00:00
struct exclude_vector *excludes,
2010-08-03 13:14:37 +00:00
int inotify_fd)
2008-08-12 11:51:46 +00:00
{
char buf[INOTIFY_BUF_LEN];
int len;
2010-05-22 15:25:31 +00:00
long clocks_per_sec = sysconf(_SC_CLK_TCK);
struct timeval tv;
fd_set readfds;
clock_t now;
clock_t alarm;
const struct log *log = &opts->log;
2010-05-22 15:25:31 +00:00
2010-08-03 12:21:21 +00:00
if (opts->delay > 0) {
2010-05-22 15:25:31 +00:00
if (clocks_per_sec <= 0) {
printlogf(log, ERROR, "Clocks per second invalid (%li)!", clocks_per_sec);
terminate(log, LSYNCD_INTERNALFAIL);
2010-05-22 15:25:31 +00:00
}
}
2008-08-12 11:51:46 +00:00
while (keep_going) {
2010-08-03 14:49:53 +00:00
int do_read;
if (delays->first && time_after_eq(times(NULL), delays->first->alarm)) {
// there is a delay that wants to be handled already
// do not read from inotify_fd and jump directly to delay handling
printlogf(log, DEBUG, "immediately handling delayed entries");
2010-08-03 14:49:53 +00:00
do_read = 0;
} else if (opts->delay > 0 && delays->first) {
2010-05-22 15:25:31 +00:00
// use select() to determine what happens first
// a new event or "alarm" of an event to actually
// call its binary. The delay with the index 0
// should have the nearest alarm time.
alarm = delays->first->alarm;
2010-05-22 15:25:31 +00:00
now = times(NULL);
tv.tv_sec = (alarm - now) / clocks_per_sec;
tv.tv_usec = (alarm - now) * 1000000 / clocks_per_sec % 1000000;
2010-08-03 12:21:21 +00:00
if (tv.tv_sec > opts->delay) {
2010-05-24 12:32:34 +00:00
// security boundary in case of times() wrap around.
2010-08-03 12:21:21 +00:00
tv.tv_sec = opts->delay;
2010-05-24 12:32:34 +00:00
tv.tv_usec = 0;
}
2010-08-03 14:49:53 +00:00
// if select returns a positive number there is data on inotify
// on zero the timemout occured.
FD_ZERO(&readfds);
FD_SET(inotify_fd, &readfds);
2010-08-03 14:49:53 +00:00
do_read = select(inotify_fd + 1, &readfds, NULL, NULL, &tv);
if (do_read) {
printlogf(log, DEBUG, "theres data on inotify.");
} else {
printlogf(log, DEBUG, "select() timeout, doing delays.");
2010-08-03 14:49:53 +00:00
}
2010-05-22 15:25:31 +00:00
} else {
// if nothing to wait for, enter a blocking read
2010-08-03 14:49:53 +00:00
printlogf(log, DEBUG, "gone blocking");
do_read = 1;
2010-05-22 15:25:31 +00:00
}
2010-08-03 14:49:53 +00:00
if (do_read) {
2010-08-03 13:14:37 +00:00
len = read (inotify_fd, buf, INOTIFY_BUF_LEN);
2010-05-22 15:25:31 +00:00
} else {
len = 0;
}
2008-08-12 11:51:46 +00:00
if (len < 0) {
if (keep_going) {
// if !keep_going it is perfectly normal to be interupted.
printlogf(log, ERROR, "read error from inotify (%d:%s)", errno, strerror(errno));
}
2008-08-12 11:51:46 +00:00
return false;
}
2010-05-22 15:25:31 +00:00
now = times(NULL);
2010-08-03 12:21:21 +00:00
alarm = now + opts->delay * clocks_per_sec;
2008-08-12 11:51:46 +00:00
{
// first handle all events read.
int i = 0;
while (i < len) {
struct inotify_event *event = (struct inotify_event *) &buf[i];
2010-08-04 20:22:38 +00:00
handle_event(opts, watches, delays, excludes, inotify_fd, event, alarm);
i += sizeof(struct inotify_event) + event->len;
}
2008-08-12 11:51:46 +00:00
}
2010-05-22 15:25:31 +00:00
// Then take of directories from the top of the delay FIFO
// until one item is found whose expiry time has not yet come
// or the stack is empty. Using now time - times(NULL) - everytime
// again as time may progresses while handling delayed entries.
while (delays->first && time_after_eq(times(NULL), delays->first->alarm)) {
rsync_dir(opts, delays->first->watch, "delay expired");
remove_first_delay(opts, delays);
2010-05-22 15:25:31 +00:00
}
2008-08-12 11:51:46 +00:00
}
2008-08-06 13:04:01 +00:00
return true;
}
/**
* Utility function to check file exists.
* Prints out error message and die.
*
* @param log logging information
* @param filename filename to check
* @param errmsg error message to print
*/
2010-08-03 13:14:37 +00:00
void
check_file_exists(const struct log* log,
const char* filename,
const char *errmsg)
{
struct stat st;
if (-1==stat(filename, &st)) {
printlogf(log, ERROR, "%s [%s] does not exist.\n", errmsg, filename);
terminate(log, LSYNCD_FILENOTFOUND);
}
}
/**
* Utility function to check given path is absolute path.
*
* @param log logging information
* @param filename filename to check
* @param errmsg filetype text to prepend to the error message.
*/
2010-08-03 13:14:37 +00:00
void
check_absolute_path(const struct log* log,
const char* filename,
const char *filetype)
{
if (filename[0] != '/') {
printlogf(log, ERROR, "%s [%s] has do be an absolute path.\n", filetype, filename);
terminate(log, LSYNCD_FILENOTFOUND);
}
}
2008-08-12 11:51:46 +00:00
/**
2008-08-06 13:04:01 +00:00
* Prints the help text and exits 0.
2008-08-12 11:51:46 +00:00
*
2008-08-06 13:04:01 +00:00
* @param arg0 argv[0] to show what lsyncd was called with.
*/
2010-08-03 13:14:37 +00:00
void
print_help(char *arg0)
2008-08-12 11:51:46 +00:00
{
printf("\n");
2008-11-20 21:38:58 +00:00
#ifdef XML_CONFIG
2008-12-05 22:27:03 +00:00
printf("USAGE: %s [OPTION]... [SOURCE] [TARGET 1] [TARGET 2] ...\n", arg0);
2008-11-20 21:38:58 +00:00
#else
2008-12-05 22:27:03 +00:00
printf("USAGE: %s [OPTION]... SOURCE TARGET-1 TARGET-2 ...\n", arg0);
2008-11-20 21:38:58 +00:00
#endif
2008-08-12 11:51:46 +00:00
printf("\n");
printf("SOURCE: a directory to watch and rsync.\n");
printf("\n");
printf("TARGET: can be any name accepted by rsync. e.g. \"foohost::barmodule/\"\n");
printf("\n");
2008-11-20 21:38:58 +00:00
#ifdef XML_CONFIG
printf("When called without SOURCE and TARGET, the\n");
printf("configuration will be read from the config file\"\n");
#endif
printf("\n");
2008-08-06 13:04:01 +00:00
printf("OPTIONS:\n");
printf(" --atomic Call the sync binary for each file instead of grouping directories\n");
printf(" --binary FILE Call this binary to sync " "(DEFAULT: %s)\n", DEFAULT_BINARY);
2008-11-20 21:38:58 +00:00
#ifdef XML_CONFIG
printf(" --conf FILE Load configuration from this file\n");
printf(" (DEFAULT: %s if called without SOURCE/TARGET)\n", DEFAULT_CONF_FILENAME);
2008-11-20 21:38:58 +00:00
#endif
2008-08-06 13:04:01 +00:00
printf(" --debug Log debug messages\n");
2010-05-22 15:25:31 +00:00
printf(" --delay SECS Delay between event and action\n");
2008-11-20 21:38:58 +00:00
printf(" --dryrun Do not call any actions, run dry only\n");
printf(" --exclude-from FILE Exclude file handled to rsync (DEFAULT: None)\n");
printf(" --help Print this help text and exit\n");
printf(" --logfile FILE Put log here (DEFAULT: uses syslog if not specified)\n");
2008-08-06 13:04:01 +00:00
printf(" --no-daemon Do not detach, log to stdout/stderr\n");
printf(" --no-startup Do not execute a startup sync (disadviced, know what you doing)\n");
printf(" --pidfile FILE Create a file containing pid of the daemon\n");
2008-08-06 13:04:01 +00:00
printf(" --scarce Only log errors\n");
printf(" --stubborn Ignore rsync errors on startup\n");
printf(" --version Print version an exit\n");
2008-08-12 11:51:46 +00:00
printf("\n");
2008-08-06 13:04:01 +00:00
printf("EXCLUDE FILE: \n");
printf(" The exclude file may have either filebased general masks like \"*.php\" without directory specifications,\n");
printf(" or exclude complete directories like \"Data/\". lsyncd will recognize directory excludes by the trailing '/'\n");
printf(" and will not add watches of directories of exactly such name including sub-directories of them.\n");
2008-08-12 11:51:46 +00:00
printf(" Please do not try to use more sophisticated exclude masks like \"Data/*.dat\" or \"Da*a/\", \"Data/Volatile/\" etc.\n");
2008-08-06 13:04:01 +00:00
printf(" This will not work like you would expect it to.\n");
2008-08-12 11:51:46 +00:00
printf("\n");
printf("LICENSE\n");
printf(" GPLv2 or any later version. See COPYING\n");
printf("\n");
2008-11-20 21:38:58 +00:00
#ifndef XML_CONFIG
printf("(this lsyncd binary was not compiled to be able to read config files)\n");
#endif
2008-08-12 11:51:46 +00:00
exit(0);
2008-08-06 13:04:01 +00:00
}
2008-11-20 21:38:58 +00:00
#ifdef XML_CONFIG
/*--------------------------------------------------------------------------*
* Config file parsing
2008-11-20 21:38:58 +00:00
*--------------------------------------------------------------------------*/
/**
* Parses <callopts>
*
* @return the allocated and filled calloptions structure
*/
2010-08-03 13:14:37 +00:00
struct call_option *
parse_callopts(struct global_options *opts, xmlNodePtr node) {
2008-11-20 21:38:58 +00:00
xmlNodePtr cnode;
xmlChar *xc;
int opt_n = 0;
struct call_option * asw;
// count how many options are there
for (cnode = node->children; cnode; cnode = cnode->next) {
if (cnode->type != XML_ELEMENT_NODE) {
continue;
}
if (xmlStrcmp(cnode->name, BAD_CAST "option") &&
xmlStrcmp(cnode->name, BAD_CAST "exclude-file") &&
xmlStrcmp(cnode->name, BAD_CAST "source") &&
xmlStrcmp(cnode->name, BAD_CAST "destination")
) {
printlogf(NULL, ERROR, "error unknown call option type \"%s\"", cnode->name);
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
opt_n++;
}
opt_n++;
2010-08-04 14:20:28 +00:00
asw = (struct call_option *) s_calloc(NULL, opt_n, sizeof(struct call_option), "call options");
2008-11-20 21:38:58 +00:00
// fill in the answer
opt_n = 0;
for (cnode = node->children; cnode; cnode = cnode->next) {
if (cnode->type != XML_ELEMENT_NODE) {
continue;
}
asw[opt_n].text = NULL;
if (!xmlStrcmp(cnode->name, BAD_CAST "option")) {
xc = xmlGetProp(cnode, BAD_CAST "text");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: text attribute missing from <option/>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
asw[opt_n].kind = CO_TEXT;
2010-08-04 14:20:28 +00:00
asw[opt_n].text = s_strdup(NULL, (char *) xc, "asw text");
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(cnode->name, BAD_CAST "exclude-file")) {
asw[opt_n].kind = CO_EXCLUDE;
} else if (!xmlStrcmp(cnode->name, BAD_CAST "source")) {
asw[opt_n].kind = CO_SOURCE;
} else if (!xmlStrcmp(cnode->name, BAD_CAST "destination")) {
asw[opt_n].kind = CO_DEST;
} else {
assert(false);
}
opt_n++;
}
asw[opt_n].text = NULL;
asw[opt_n].kind = CO_EOL;
return asw;
}
2010-08-05 11:22:12 +00:00
/**
* Parses <inotify>
*/
uint32_t
parse_inotify(xmlNodePtr node) {
xmlNodePtr dnode;
xmlChar *xc;
uint32_t mask = 0;
int id = 0;
for (dnode = node->children; dnode; dnode = dnode->next) {
if (dnode->type != XML_ELEMENT_NODE) {
continue;
}
if (!xmlStrcmp(dnode->name, BAD_CAST "event")) {
xc = xmlGetProp(dnode, BAD_CAST "id");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute id missing from <event>\n");
exit(LSYNCD_BADCONFIGFILE);
}
id = event_text_to_mask((char*) xc);
if (!id) {
printlogf(NULL, ERROR, "error in config file: attribute id of <event>: \"%s\" not known.\n", (char*) xc);
exit(LSYNCD_BADCONFIGFILE);
}
mask |= id;
} else {
printlogf(NULL, ERROR, "error in config file: unknown node in <inotify> \"%s\"\n", dnode->name);
exit(LSYNCD_BADCONFIGFILE);
}
}
if (!mask) {
printlogf(NULL, ERROR, "error in config file: no valid <event> node in <inotify>\n");
exit(LSYNCD_BADCONFIGFILE);
}
return mask;
}
2008-11-20 21:38:58 +00:00
/**
* Parses <diretory>
*/
2010-08-03 13:14:37 +00:00
bool
parse_directory(struct global_options *opts, xmlNodePtr node) {
2008-11-20 21:38:58 +00:00
xmlNodePtr dnode;
xmlChar *xc;
struct dir_conf * dc = new_dir_conf(opts);
2008-11-20 21:38:58 +00:00
for (dnode = node->children; dnode; dnode = dnode->next) {
if (dnode->type != XML_ELEMENT_NODE) {
continue;
}
if (!xmlStrcmp(dnode->name, BAD_CAST "source")) {
xc = xmlGetProp(dnode, BAD_CAST "path");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute path missing from <source>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
}
if (dc->source) {
printlogf(NULL, ERROR, "error in config file: cannot have more than one source in one <directory>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
// TODO: use realdir() on xc
2010-08-04 14:20:28 +00:00
dc->source = s_strdup(NULL, (char *) xc, "xml source");
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(dnode->name, BAD_CAST "target")) {
xc = xmlGetProp(dnode, BAD_CAST "path");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute path missing from <target>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
}
dir_conf_add_target(NULL, dc, (char *) xc);
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(dnode->name, BAD_CAST "binary")) {
xc = xmlGetProp(dnode, BAD_CAST "filename");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute filename missing from <binary>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
2010-08-04 14:20:28 +00:00
dc->binary = s_strdup(NULL, (char *) xc, "xml binary");
2010-08-05 11:22:12 +00:00
} else if (!xmlStrcmp(dnode->name, BAD_CAST "callopts")) {
if (dc->callopts) {
printlogf(NULL, ERROR, "error in config file: there is more than one <callopts> in a <directory>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
}
dc->callopts = parse_callopts(opts, dnode);
2010-05-22 12:05:31 +00:00
} else if (!xmlStrcmp(dnode->name, BAD_CAST "exclude-from")) {
xc = xmlGetProp(dnode, BAD_CAST "filename");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute filename missing from <exclude-from>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2010-05-22 12:05:31 +00:00
}
2010-08-04 14:20:28 +00:00
dc->exclude_file = s_strdup(NULL, (char *) xc, "xml exclude");
2010-08-05 11:22:12 +00:00
} else if (!xmlStrcmp(dnode->name, BAD_CAST "inotify")) {
if (dc->event_mask) {
fprintf(stderr, "error in config file: there is more than one <inotify> in a <directory>\n");
exit(LSYNCD_BADCONFIGFILE);
}
dc->event_mask = parse_inotify(dnode);
2008-11-20 21:38:58 +00:00
} else {
printlogf(NULL, ERROR, "error in config file: unknown node in <directory> \"%s\"\n", dnode->name);
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
}
if (!dc->source) {
printlogf(NULL, ERROR, "error in config file: source missing from <directory>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
}
2008-12-05 22:27:03 +00:00
if (dc->targets[0] == NULL) {
printlogf(NULL, ERROR, "error in config file: target missing from <directory>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
}
2008-11-20 21:38:58 +00:00
return true;
}
/**
* Parses <settings>
*/
2010-08-03 13:14:37 +00:00
bool
parse_settings(struct global_options *opts, xmlNodePtr node) {
2008-11-20 21:38:58 +00:00
xmlNodePtr snode;
xmlChar *xc;
for (snode = node->children; snode; snode = snode->next) {
if (snode->type != XML_ELEMENT_NODE) {
continue;
}
if (!xmlStrcmp(snode->name, BAD_CAST "atomic")) {
opts->flag_atomic = 1;
} else if (!xmlStrcmp(snode->name, BAD_CAST "debug")) {
opts->log.loglevel = 1;
2010-05-22 15:25:31 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "delay")) {
char *p;
xc = xmlGetProp(snode, BAD_CAST "value");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute value missing from <delay/>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2010-05-22 15:25:31 +00:00
}
2010-08-03 12:21:21 +00:00
opts->delay = strtol((char *) xc, &p, 10);
2010-05-22 15:25:31 +00:00
if (*p) {
printlogf(NULL, ERROR, "<delay> value %s is not an integer.\n", xc);
terminate(NULL, LSYNCD_BADCONFIGFILE);
2010-05-22 15:25:31 +00:00
}
2010-08-03 12:21:21 +00:00
if (opts->delay < 0) {
printlogf(NULL, ERROR, "<delay> value may not be negative.\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2010-05-22 15:25:31 +00:00
}
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "dryrun")) {
opts->flag_dryrun = 1;
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "exclude-from")) {
xc = xmlGetProp(snode, BAD_CAST "filename");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute filename missing from <exclude-from/>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
2010-08-04 14:20:28 +00:00
opts->default_exclude_file = s_strdup(NULL, (char *) xc, "xml default-exclude-file");
2010-08-05 11:22:12 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "inotify")) {
if (opts->default_event_mask) {
printlogf(NULL, ERROR, "error in config file: there is more than one <inotify> in a <directory>\n");
exit(LSYNCD_BADCONFIGFILE);
}
opts->default_event_mask = parse_inotify(snode);
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "logfile")) {
xc = xmlGetProp(snode, BAD_CAST "filename");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute filename missing from <logfile/>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
2010-08-04 14:20:28 +00:00
opts->log.logfile = s_strdup(NULL, (char *) xc, "xml logfile");
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "binary")) {
xc = xmlGetProp(snode, BAD_CAST "filename");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute filename missing from <binary/>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
2010-08-04 14:20:28 +00:00
opts->default_binary = s_strdup(NULL, (char *) xc, "xml default-binary");
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "pidfile")) {
xc = xmlGetProp(snode, BAD_CAST "filename");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: attribute filename missing from <pidfile/>\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
2010-08-04 14:20:28 +00:00
opts->pidfile = s_strdup(NULL, (char *) xc, "xml pidfile");
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "callopts")) {
opts->default_callopts = parse_callopts(opts, snode);
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "scarce")) {
opts->log.loglevel = 3;
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(snode->name, BAD_CAST "no-daemon")) {
opts->log.flag_nodaemon = 1;
} else if (!xmlStrcmp(snode->name, BAD_CAST "no-startup")) {
opts->flag_nostartup = 1;
} else if (!xmlStrcmp(snode->name, BAD_CAST "stubborn")) {
opts->flag_stubborn = 1;
2008-11-20 21:38:58 +00:00
} else {
printlogf(NULL, ERROR, "error unknown node in <settings> \"%s\"", snode->name);
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
}
return true;
}
/**
* Parses the config file specified in the global variable
* conf_filename, fills the global options value according
* to the config file.
*
* @param fullparse if false only read globals.
*/
2010-08-03 13:14:37 +00:00
bool
parse_config(struct global_options *opts, bool fullparse) {
2008-11-20 21:38:58 +00:00
LIBXML_TEST_VERSION
xmlDoc *doc = NULL;
xmlNode *root_element = NULL;
xmlNodePtr node;
xmlChar *xc;
doc = xmlReadFile(opts->conf_filename, NULL, 0);
2008-11-20 21:38:58 +00:00
if (doc == NULL) {
printlogf(NULL, ERROR, "error: could not parse config file \"%s\"\n", opts->conf_filename);
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
root_element = xmlDocGetRootElement(doc);
// check version specifier
if (xmlStrcmp(root_element->name, BAD_CAST "lsyncd")) {
printlogf(NULL, ERROR, "error in config file: root node is not \"lsyncd\".\n");
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
xc = xmlGetProp(root_element, BAD_CAST "version");
if (xc == NULL) {
printlogf(NULL, ERROR, "error in config file: version specifier missing in \"%s\",\n", opts->conf_filename);
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
2009-01-05 13:45:12 +00:00
if (xmlStrcmp(xc, BAD_CAST "1") && xmlStrcmp(xc, BAD_CAST "1.25")) { //1.25, backward stuff
printlogf(NULL, ERROR, "error in config file: expected a \"1\" versioned file, found \"%s\"\n", xc);
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
for (node = root_element->children; node; node = node->next) {
if (node->type != XML_ELEMENT_NODE) {
continue;
}
if (!xmlStrcmp(node->name, BAD_CAST "settings")) {
parse_settings(opts, node);
2008-11-20 21:38:58 +00:00
} else if (!xmlStrcmp(node->name, BAD_CAST "directory")) {
if (fullparse) {
parse_directory(opts, node);
2008-11-20 21:38:58 +00:00
}
} else {
printlogf(NULL, ERROR, "error in config file: unknown node in <lsyncd> \"%s\"\n", node->name);
terminate(NULL, LSYNCD_BADCONFIGFILE);
2008-11-20 21:38:58 +00:00
}
}
xmlFreeDoc(doc);
xmlCleanupParser();
2008-11-20 21:38:58 +00:00
return true;
}
#endif
2008-08-06 13:04:01 +00:00
/**
* Parses the command line options.
*
* terminates in some cases of badparameters, or on
* --version or --help
2008-08-06 13:04:01 +00:00
*/
2010-08-03 13:14:37 +00:00
void
parse_options(struct global_options *opts, int argc, char **argv)
2008-08-12 11:51:46 +00:00
{
2008-12-05 22:27:03 +00:00
char **target;
2008-08-12 11:51:46 +00:00
static struct option long_options[] = {
{"atomic", 1, NULL, 0},
{"binary", 1, NULL, 0},
2008-11-20 21:38:58 +00:00
#ifdef XML_CONFIG
{"conf", 1, NULL, 0},
2008-11-20 21:38:58 +00:00
#endif
{"debug", 0, NULL, 1},
{"delay", 1, NULL, 0},
{"dryrun", 0, NULL, 1},
{"exclude-from", 1, NULL, 0},
{"help", 0, NULL, 0},
{"logfile", 1, NULL, 0},
{"no-daemon", 0, NULL, 1},
{"no-startup", 0, NULL, 1},
{"pidfile", 1, NULL, 0},
{"scarce", 0, NULL, 3},
{"stubborn", 0, NULL, 1},
{"version", 0, NULL, 0},
{NULL, 0, NULL, 0}
2008-08-12 11:51:46 +00:00
};
bool read_conf = false;
2008-08-12 11:51:46 +00:00
{
// replace NULL targets with actual targets
// because compiler wont allow to init with them.
struct option *o;
for(o = long_options; o->name; o++) {
if (!strcmp("atomic", o->name)) o->flag = &opts->flag_atomic;
if (!strcmp("debug", o->name)) o->flag = &opts->log.loglevel;
if (!strcmp("dryrun", o->name)) o->flag = &opts->flag_dryrun;
if (!strcmp("no-daemon", o->name)) o->flag = &opts->log.flag_nodaemon;
if (!strcmp("no-startup", o->name)) o->flag = &opts->flag_nostartup;
if (!strcmp("scarce", o->name)) o->flag = &opts->log.loglevel;
if (!strcmp("stubborn", o->name)) o->flag = &opts->flag_stubborn;
}
}
2008-11-20 21:38:58 +00:00
#ifdef XML_CONFIG
// First determine if the config file should be read at all.
// If so, read it before parsing all other options in detail,
// because command line options should overwrite settings in
// the confing file.
//
2008-11-20 21:38:58 +00:00
// There are 2 conditions in which the conf file is read, either
// --conf FILE is given as option, or there isn't a SOURCE and
// DESTINATION given, in which getting the config from the conf
// file will be the default option.
2008-08-12 11:51:46 +00:00
while (1) {
int oi = 0;
2008-11-20 21:38:58 +00:00
int c = getopt_long_only(argc, argv, "", long_options, &oi);
2008-08-12 11:51:46 +00:00
if (c == -1) {
break;
}
if (c == '?') {
terminate(NULL, LSYNCD_BADPARAMETERS);
2008-08-12 11:51:46 +00:00
}
if (c == 0) { // longoption
2008-11-20 21:38:58 +00:00
if (!strcmp("conf", long_options[oi].name)) {
read_conf = true;
2010-08-04 14:20:28 +00:00
opts->conf_filename = s_strdup(NULL, optarg, "opt conf-filename");
2008-11-20 21:38:58 +00:00
}
2008-08-12 11:51:46 +00:00
if (!strcmp("help", long_options[oi].name)) {
2008-11-20 21:38:58 +00:00
// in case --help do not go further, or else
// lsyncd would complain of not being configured ...
2008-08-12 11:51:46 +00:00
print_help(argv[0]);
}
if (!strcmp("version", long_options[oi].name)) {
2008-11-20 21:38:58 +00:00
// same here
printf("Version: %s\n", VERSION);
terminate(NULL, LSYNCD_SUCCESS);
2008-08-12 11:51:46 +00:00
}
2008-11-20 21:38:58 +00:00
}
}
if (read_conf) {
// parse config file, when additional source/dest parameters are
// given on the command line, then the directory settings
// in the config file are ignored.
parse_config(opts, optind == argc);
2008-11-20 21:38:58 +00:00
} else if (optind == argc) {
// when no config file is specified and there are also
// no source/targets, read the default config file.
parse_config(opts, true);
2008-11-20 21:38:58 +00:00
}
// resets the get option parser
2008-11-20 21:38:58 +00:00
optind = 1;
#endif
// now parse all the other options normally.
2008-11-20 21:38:58 +00:00
while (1) {
int oi = 0;
int c = getopt_long_only(argc, argv, "", long_options, &oi);
if (c == -1) {
break;
}
2008-08-12 11:51:46 +00:00
2008-11-20 21:38:58 +00:00
if (c == '?') {
terminate(NULL, LSYNCD_BADPARAMETERS);
2008-11-20 21:38:58 +00:00
}
2008-08-12 11:51:46 +00:00
2008-11-20 21:38:58 +00:00
if (c == 0) { // longoption
if (!strcmp("binary", long_options[oi].name)) {
2010-08-04 14:20:28 +00:00
opts->default_binary = s_strdup(NULL, optarg, "opt default-binary");
2008-11-20 21:38:58 +00:00
}
2010-05-22 15:25:31 +00:00
if (!strcmp("delay", long_options[oi].name)) {
char *p;
2010-08-03 12:21:21 +00:00
opts->delay = strtol(optarg, &p, 10);
2010-05-22 15:25:31 +00:00
if (*p) {
printf("%s is not an integer.\n", optarg);
terminate(NULL, LSYNCD_BADPARAMETERS);
2010-05-22 15:25:31 +00:00
}
2010-08-03 12:21:21 +00:00
if (opts->delay < 0) {
2010-05-22 15:25:31 +00:00
printf("delay may not be negative.\n");
terminate(NULL, LSYNCD_BADPARAMETERS);
2010-05-22 15:25:31 +00:00
}
}
2008-08-12 11:51:46 +00:00
if (!strcmp("exclude-from", long_options[oi].name)) {
2010-08-04 14:20:28 +00:00
opts->default_exclude_file = s_strdup(NULL, optarg, "opt default-exclude-file");
2008-08-12 11:51:46 +00:00
}
2008-10-06 20:35:52 +00:00
2008-11-20 21:38:58 +00:00
if (!strcmp("help", long_options[oi].name)) {
print_help(argv[0]);
2008-10-06 20:35:52 +00:00
}
2008-11-20 21:38:58 +00:00
if (!strcmp("logfile", long_options[oi].name)) {
2010-08-04 14:20:28 +00:00
opts->log.logfile = s_strdup(NULL, optarg, "opt logfile");
2008-11-20 21:38:58 +00:00
}
if (!strcmp("pidfile", long_options[oi].name)) {
2010-08-04 14:20:28 +00:00
opts->pidfile = s_strdup(NULL, optarg, "opt pidfile");
}
2008-11-20 21:38:58 +00:00
if (!strcmp("version", long_options[oi].name)) {
printf("Version: %s\n", VERSION);
terminate(NULL, LSYNCD_SUCCESS);
2008-11-20 21:38:58 +00:00
}
2008-08-12 11:51:46 +00:00
}
}
2008-11-20 21:38:58 +00:00
// If the config file specified something to do already
// dir_conf_n will already be > 0
if (opts->dir_conf_n == 0) {
2008-11-20 21:38:58 +00:00
struct dir_conf * odc; // dir_conf specified by command line options.
2008-12-05 22:27:03 +00:00
bool first_target = true;
if (optind + 2 > argc) {
printf("Error: please specify SOURCE and at least one TARGET (see --help)\n");
2008-11-20 21:38:58 +00:00
#ifdef XML_CONFIG
printf(" or at least one <directory> entry in the conf file.\n");
2008-11-20 21:38:58 +00:00
#endif
terminate(NULL, LSYNCD_BADPARAMETERS);
2008-11-20 21:38:58 +00:00
}
odc = new_dir_conf(opts);
2008-11-20 21:38:58 +00:00
/* Resolves relative source path, lsyncd might chdir to / later. */
odc->source = realdir(NULL, argv[optind]);
2008-11-20 21:38:58 +00:00
if (!odc->source) {
printf("Error: Source [%s] not found or not a directory.\n", argv[optind]);
terminate(NULL, LSYNCD_FILENOTFOUND);
2008-11-20 21:38:58 +00:00
}
2008-12-05 22:27:03 +00:00
for (target = &argv[optind + 1]; *target; target++) {
dir_conf_add_target(NULL, odc, *target);
2008-12-05 22:27:03 +00:00
if (first_target) {
printlogf(&opts->log, NORMAL, "command line options: syncing %s -> %s",
2008-12-05 23:54:13 +00:00
odc->source, *target);
2008-12-05 22:27:03 +00:00
first_target = false;
} else {
printlogf(&opts->log, NORMAL, " and -> %s",
2008-12-05 23:54:13 +00:00
*target);
2008-12-05 22:27:03 +00:00
}
}
2008-10-06 20:35:52 +00:00
}
// some sanity checks
if (opts->default_exclude_file) {
check_absolute_path(NULL, opts->default_exclude_file, "Exclude file");
check_file_exists (NULL, opts->default_exclude_file, "Exclude file");
}
if (opts->pidfile) {
check_absolute_path(NULL, opts->pidfile, "Pid file");
}
if (opts->flag_stubborn && opts->flag_nostartup) {
printlogf(&opts->log, NORMAL, "Warning: specifying 'stubborn' when skipping with 'no-startup' has no effect.");
}
2008-08-06 13:04:01 +00:00
}
/**
* Parses the exclude file looking for directory masks to not watch.
2010-08-04 20:22:38 +00:00
*
*
2008-08-06 13:04:01 +00:00
*/
2010-08-03 13:14:37 +00:00
bool
2010-08-04 20:22:38 +00:00
parse_exclude_file(struct log *log,
struct exclude_vector * excludes,
char *filename) {
2008-08-12 11:51:46 +00:00
FILE * ef;
char line[PATH_MAX+1];
2008-08-06 13:04:01 +00:00
int sl;
2008-11-20 21:38:58 +00:00
ef = fopen(filename, "r");
2008-08-06 13:04:01 +00:00
if (ef == NULL) {
printlogf(log, ERROR, "Cannot open exclude file '%s'\n", filename);
terminate(log, LSYNCD_FILENOTFOUND);
2008-08-12 11:51:46 +00:00
}
while (1) {
if (!fgets(line, sizeof(line), ef)) {
if (feof(ef)) {
fclose(ef);
return true;
}
printlogf(log, ERROR, "Reading file '%s' (%d:%s)\n",
2008-11-20 21:38:58 +00:00
filename, errno, strerror(errno));
2008-08-12 11:51:46 +00:00
terminate(log, LSYNCD_FILENOTFOUND);
2008-08-12 11:51:46 +00:00
}
2010-05-20 12:42:58 +00:00
2008-08-12 11:51:46 +00:00
sl = strlen(line);
if (sl == 0) {
continue;
}
2008-08-06 13:04:01 +00:00
if (line[sl - 1] == '\n') {
2008-08-12 11:51:46 +00:00
line[sl - 1] = 0;
2008-08-06 13:04:01 +00:00
sl--;
}
2008-08-12 11:51:46 +00:00
if (sl == 0) {
continue;
}
if (line[sl - 1] == '/') {
2010-08-04 20:22:38 +00:00
if (excludes->len + 1 >= MAX_EXCLUDES) {
printlogf(log, ERROR,
2008-11-20 21:38:58 +00:00
"Too many directory excludes, can only have %d at the most",
MAX_EXCLUDES);
terminate(log, LSYNCD_TOOMANYDIRECTORYEXCLUDES);
2008-08-12 11:51:46 +00:00
}
line[sl - 1] = 0;
2008-08-06 13:04:01 +00:00
sl--;
2008-08-12 11:51:46 +00:00
if (sl == 0) {
continue;
}
printlogf(log, NORMAL, "Excluding directories of the name '%s'", line);
2008-08-12 11:51:46 +00:00
2010-08-04 20:22:38 +00:00
excludes->data[excludes->len] = s_malloc(log, strlen(line) + 1, "exclude_dir");
strcpy(excludes->data[excludes->len], line);
excludes->len++;
2008-08-12 11:51:46 +00:00
}
}
return true;
2008-08-06 13:04:01 +00:00
}
2008-11-20 21:38:58 +00:00
/**
2010-08-03 13:14:37 +00:00
* Writes a pid file.
2008-11-20 21:38:58 +00:00
*/
2010-08-03 13:14:37 +00:00
void
write_pidfile(const struct log *log, const char *pidfile) {
FILE* f = fopen(pidfile, "w");
if (!f) {
printlogf(log, ERROR, "Error: cannot write pidfile [%s]\n", pidfile);
terminate(log, LSYNCD_FILENOTFOUND);
}
fprintf(f, "%i\n", getpid());
fclose(f);
}
2008-08-06 13:04:01 +00:00
/**
2010-08-04 20:22:38 +00:00
* Main for one run.
* Can be runned through several times on HUPs.
2008-08-06 13:04:01 +00:00
*/
2010-08-03 13:14:37 +00:00
int
2010-08-04 20:22:38 +00:00
one_main(int argc, char **argv)
2008-08-12 11:51:46 +00:00
{
2010-08-04 20:22:38 +00:00
struct global_options opts = {{0,}}; // global options
struct log *log = &opts.log; // shortcut to logging options.
struct watch_vector watches = {0, }; // all watches
struct delay_vector delays = {0, }; // delayed entries
struct exclude_vector excludes = {{0, }}; // excludes
int inotify_fd; // inotify file descriptor
openlog("lsyncd", LOG_CONS | LOG_PID, LOG_DAEMON);
2008-11-20 21:38:58 +00:00
reset_options(&opts);
parse_options(&opts, argc, argv);
2008-08-12 11:51:46 +00:00
if (opts.default_exclude_file) {
2010-08-04 20:22:38 +00:00
parse_exclude_file(log, &excludes, opts.default_exclude_file);
2008-08-12 11:51:46 +00:00
}
2010-08-03 13:14:37 +00:00
inotify_fd = inotify_init();
if (inotify_fd == -1) {
printlogf(log, ERROR, "Cannot create inotify instance! (%d:%s)",
2008-11-20 21:38:58 +00:00
errno, strerror(errno));
return LSYNCD_NOINOTIFY;
2008-08-12 11:51:46 +00:00
}
if (!opts.log.flag_nodaemon) {
2008-11-20 21:38:58 +00:00
// this will make this process child of init
// close stdin/stdout/stderr and
// chdir to /
if (daemon(0, 0)) {
printlogf(log, ERROR, "Cannot daemonize! (%d:%s)",
errno, strerror(errno));
return LSYNCD_INTERNALFAIL;
}
2008-08-12 11:51:46 +00:00
}
printlogf(log, NORMAL, "--- Starting up ---");
2008-08-12 11:51:46 +00:00
if (opts.pidfile) {
write_pidfile(log, opts.pidfile);
}
2010-08-04 11:57:35 +00:00
watches.size = VECT_INIT_SIZE;
2010-08-04 14:20:28 +00:00
watches.data = s_calloc(log, watches.size, sizeof(struct watch *), "watches vector");
2010-08-04 10:52:31 +00:00
delays.first = delays.last = NULL;
2008-08-12 11:51:46 +00:00
{
// add all watches
int i;
for (i = 0; i < opts.dir_conf_n; i++) {
printlogf(log, NORMAL, "watching %s", opts.dir_confs[i].source);
2010-08-04 20:22:38 +00:00
add_dirwatch(&opts, &watches, &delays, &excludes, inotify_fd,
opts.dir_confs[i].source, NULL, &opts.dir_confs[i], false);
2010-08-03 14:16:12 +00:00
}
}
2008-11-20 21:38:58 +00:00
// startup recursive sync.
if (!opts.flag_nostartup) {
int i;
for (i = 0; i < opts.dir_conf_n; i++) {
char **target;
for (target = opts.dir_confs[i].targets; *target; ++target) {
2010-08-03 14:16:12 +00:00
printlogf(log, NORMAL, "Initial recursive sync for %s -> %s", opts.dir_confs[i].source, *target);
if (!action(&opts, &opts.dir_confs[i], opts.dir_confs[i].source, *target, true)) {
2010-08-03 14:16:12 +00:00
printlogf(log, ERROR, "Initial rsync from %s -> %s failed%s",
opts.dir_confs[i].source, *target,
opts.flag_stubborn ? ", but continuing because being stubborn." : ".");
if (!opts.flag_stubborn) {
terminate(log, LSYNCD_EXECFAIL);
}
}
2008-12-05 22:27:03 +00:00
}
}
} else {
printlogf(log, NORMAL, "Skipped startup since nostartup flag is turned on.");
2008-08-06 13:04:01 +00:00
}
printlogf(log, NORMAL,
2008-11-20 21:38:58 +00:00
"--- Entering normal operation with [%d] monitored directories ---",
watches.len);
2008-08-06 13:04:01 +00:00
signal(SIGTERM, catch_alarm);
2010-08-04 20:22:38 +00:00
signal(SIGHUP, catch_alarm);
2010-08-04 20:22:38 +00:00
master_loop(&opts, &watches, &delays, &excludes, inotify_fd);
2008-08-12 11:51:46 +00:00
2010-08-04 20:22:38 +00:00
if (!termed) {
printlogf(log, NORMAL, "--- Received HUP-Signal, cleaning up and starting over ---");
}
2010-08-04 14:20:28 +00:00
{
// memory clean up
int i;
struct delay * d;
2010-08-04 14:20:28 +00:00
reset_options(&opts);
for(i = 0; i < watches.len; i++) {
if (watches.data[i]->dirname) {
s_free(watches.data[i]->dirname);
}
s_free(watches.data[i]);
}
s_free(watches.data);
for(d = delays.first; d; d = d->next) {
s_free(d);
}
2010-08-04 20:22:38 +00:00
for(i = 0; i < excludes.len; i++) {
s_free(excludes.data[i]);
}
2010-08-04 14:20:28 +00:00
}
#ifdef MEMCHECK
fprintf(stderr, "Memcheck count: %d\n", memc);
twalk(mroot, maction);
fprintf(stderr, "Memcheck count: %d\n", memc);
#endif
2008-08-12 11:51:46 +00:00
return 0;
2008-08-06 13:04:01 +00:00
}
2010-08-04 20:22:38 +00:00
/**
* Main wrapper
*
* Start actual main over and over on HUPs.
*/
int
main(int argc, char **argv)
{
int ret;
fprintf(stderr, "WARNING: this is an experimental version, do not use it for anything semi-important\n");
2010-08-04 20:22:38 +00:00
do {
ret = one_main(argc, argv);
if (ret) {
return ret;
}
// start over
keep_going = 1;
} while (!termed);
return ret;
}