/*
Program : watchd
Created : 20.08.2001
Modified : $Date: 2005/05/10 04:08:15 $
Author : Peter Turczak <p_turczak@wiwa.de>
Syntax : watchd
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define __USE_BSD
#include <sys/types.h>
#include <sys/timeb.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "inifile.h"
#include "chain.h"
#include "filedb.h"
#define FL_ACT_CHANGE 1
#define FL_ACT_EXISTENCE 2
#define FL_ACT_NOMOVE 4
#define FL_ACT_COPY 8
#define FL_ACT_DELETE 16
#define FL_LOG_SYSLOG 64
#define FL_LOG_FILE 128
#define FL_LOG_MAIL 256
#define FL_LOG_ALWAYS 512
typedef struct Twatchfolder
{
char* runprg;
char* dir;
element *filechain;
uid_t user;
int flags;
char *notify;
char *logfile;
int gracetime; // Remaining time 'till scanchain
int interval, curcount; // Interval will stay constant,
// curcount-- until exec then curcount=interval
} Twatchfolder;
Twatchfolder defaultfolder;
Twatchfolder *curfolder=&defaultfolder;
element *folders; //Chain of folders
int interval=1; // Global rescan interval in seconds
int timeofgrace;// Time in intervals between calls to scanchain
char *tmpdir; // Directory where the processing dirs are created. Default=/tmp
char *mvprg; // Program that is used to move the files around. Default=mv
char *cpprg; // Program that is used to move the files around. Default=cp
char *rmprg; // Program that is used to remove the files. Default=rm
char *config_file = NULL;
static char default_config_filename[] = "/usr/local/etc/watchd.conf";
#ifdef HAS_NO_MKSTEMP
int mkstemp(char *template)
{
int r;
char *dir=strdup(template);
char *prefix=NULL;
if (strrchr(dir, '/')!=0)
{
file=strrchr(dir, '/');
file=0x00;
file++;
if (strstr(file, "XXXXXX"))
{
(strstr(file, "XXXXXX"))[0]=0x00;
}
}
r=open(tempnam(dir, prefix), O_CREAT | O_RDWR | S_IRWXU);
return(r);
}
#endif
int get_filestate(char *fn)
{
int fd=open(fn,O_RDWR);
/* l_type l_whence l_start l_len l_pid */
struct flock fl = { F_WRLCK, SEEK_SET, 0, 0, 0 };
if (fd>0)
{
fl.l_pid = getpid();
fl.l_type = F_WRLCK;
if (fcntl(fd, F_SETLK, &fl) == -1)
{
close(fd);
#ifdef DEBUG
printf("get_filestate(%s)=%d\n",fn,0);
#endif
return(0);
} else {
close(fd);
#ifdef DEBUG
printf("get_filestate(%s)=%d\n",fn,1);
#endif
fl.l_type = F_UNLCK; /* set to unlock same region */
if (fcntl(fd, F_SETLK, &fl) == -1)
{
close(fd);
#ifdef DEBUG
printf("Strange unlock behavior..\n");
#endif
}
return(1);
}
} else // open() failed...
{
if (isdir(fn))
{
#ifdef DEBUG
printf("Directory %s not processed...\n",fn);
#endif
return(2);
}
else
return(1);
}
}
int splits(char *s, char* p1, char* p2)
{
char *equal=strchr(s,'=');
char *p2tmp;
int pos1 = (((equal==NULL)||(equal<s))?0:(equal-s));
s[pos1]=0x00; // Needed, because strncpy is too stupid to
// null-terminate a substring by itself!
strcpy(p1,"");
strcpy(p2,"");
if (pos1==0) return(1); else
{
strncpy(p1, s, pos1+1);
strcpy(p2, equal+1);
// p2tmp=malloc(strlen(p2));
// strncpy(p2tmp,p2,strlen(p2)-1);
// strcpy(p2,p2tmp);
// free(p2tmp);
p2tmp=strchr(p2,0xa); // Not very nice,
p2[(int)p2tmp-(int)p2]=0x00; // but in fact: It works ;)
while (p2[0]==' ') {p2++;} // Again, quick and dirty..
while (p1[strlen(p1)-1]==' ') {p1[strlen(p1)-1]=0;} // Need to say anything?;)
#ifdef DEBUG
printf("split(s='%s',p1='%s',p2='%s',pos1=%d);\n",s,p1,p2,pos1);
#endif
return(0);
}
}
void addfolder(Twatchfolder *f, char *dir, char *prg, uid_t uid, int interval, int flags)
{
if (f==NULL)
{
folders=addelement(&defaultfolder, sizeof(defaultfolder), folders);
folders=tail(folders);
curfolder=(Twatchfolder *)folders->cur;
curfolder->filechain=newchain();
f=curfolder;
}
if (prg!=NULL) f->runprg=strdup(prg);
if (dir!=NULL) f->dir=strdup(dir);
if (uid!=-1) f->user=uid;
if (flags!=0) f->flags=flags;
if (interval!=-1)
{
f->interval=interval;
f->curcount=interval;
}
}
int flags2int(char *c)
{
int r=0;
char *i=(char *)malloc(strlen(c)+3);
char *s=i;
char *p;
strcpy(s, c);
strcat(s, " ");
while ((p=strstr(s," "))!=NULL)
{
p[0]=0x00;
if (strncasecmp("CHANGE",s, 6)==0)
r|=FL_ACT_CHANGE;
if (strncasecmp("EXIST",s, 5)==0)
r|=FL_ACT_EXISTENCE;
if (strncasecmp("COPY",s, 4)==0)
r|=FL_ACT_COPY;
if (strncasecmp("DELETE",s, 6)==0)
r|=FL_ACT_DELETE;
if (strncasecmp("SYSLOG",s, 6)==0)
r|=FL_LOG_SYSLOG;
if (strncasecmp("FILELOG",s, 7)==0)
r|=FL_LOG_FILE;
if (strncasecmp("MAILLOG",s, 7)==0)
{
r|=FL_LOG_MAIL;
printf("While parsing logfile: Warning: MAILLOG is not yet implemented!\n");
}
if (strncasecmp("ALWAYSLOG",s, 9)==0)
r|=FL_LOG_ALWAYS;
#ifdef DEBUG
printf("flags2int('%s') : parsed '%s', r=%d\n", c, s, r);
#endif
s=++p;
}
free(i);
return(r);
}
void lowercase(char *c)
{
int i;
for (i=0;i<strlen(c);i++)
{
c[i]=tolower(c[i]);
}
}
void parseequal(char* buf)
{
char *tmpc1;
char *tmpc2;
char *tmpc3;
int tmpi; // a place to store temorary integers ...
tmpc1=malloc(strlen(buf)+1);
tmpc2=malloc(strlen(buf)+1);
splits(buf, tmpc1, tmpc2);
lowercase(tmpc1); // Drop the case on the left side...
#ifdef DEBUG
printf("lowercase returned '%s'!\n",tmpc1);
#endif
if (strncmp(tmpc1,"prg",3)==0)
{
#ifdef DEBUG
printf("'prg' line found. Adding program...\n");
#endif
addfolder(curfolder, NULL, tmpc2, -1, -1, 0);
}
if (strncmp(tmpc1,"dir",3)==0)
{
#ifdef DEBUG
printf("'dir' line found. Adding directory...\n");
#endif
addfolder(curfolder, tmpc2, NULL, -1, -1, 0);
}
if (strncmp(tmpc1,"uid",3)==0)
{
#ifdef DEBUG
printf("'uid' line found. Modifying program...\n");
#endif
addfolder(curfolder,NULL,NULL,atoi(tmpc2), -1, 0);
}
if (strncmp(tmpc1,"flags",5)==0)
{
#ifdef DEBUG
printf("'flags' line found. Modifying program...\n");
#endif
addfolder(curfolder,NULL,NULL, -1, -1, flags2int(tmpc2));
}
if (strncmp(tmpc1,"interval",8)==0)
{
#ifdef DEBUG
printf("'interval' line found. Trying to set interval...\n");
#endif
tmpc3=tmpc2;
tmpi=atoi(tmpc3);
if (tmpi>0) {
addfolder(curfolder, NULL, NULL, -1, tmpi, 0);
} // set interval if the value is not completely
// brain-damaged!
#ifdef DEBUG
printf("set to %d!\n",interval);
#endif
}
if (strncmp(tmpc1,"tempdir",7)==0)
{
#ifdef DEBUG
printf("'tempdir' line found. Trying to set tempdir...");
#endif
strcpy(tmpdir,tmpc2);
if (tmpdir[strlen(tmpdir)-1]!='/') {strcat(tmpdir,"/");}
#ifdef DEBUG
printf("set to %s!\n",tmpdir);
#endif
}
if (strncmp(tmpc1,"mv",2)==0)
{
#ifdef DEBUG
printf("'mv' line found. Trying to set mvprg...");
#endif
strcpy(mvprg,tmpc2);
#ifdef DEBUG
printf("set to %s!\n",mvprg);
#endif
}
if (strncmp(tmpc1,"rm",2)==0)
{
#ifdef DEBUG
printf("'rm' line found. Trying to set rmprg...");
#endif
strcpy(rmprg,tmpc2);
#ifdef DEBUG
printf("set to %s!\n",rmprg);
#endif
}
if (strncmp(tmpc1,"cp",2)==0)
{
#ifdef DEBUG
printf("'cp' line found. Trying to set cpprg...");
#endif
strcpy(cpprg,tmpc2);
#ifdef DEBUG
printf("set to %s!\n",cpprg);
#endif
}
if (strncmp(tmpc1,"mail",4)==0)
{
#ifdef DEBUG
printf("'mail' line found. Trying to set notify...");
#endif
curfolder->notify=strdup(tmpc2);
#ifdef DEBUG
printf("set to %s!\n",cpprg);
#endif
}
if (strncmp(tmpc1,"logfile",7)==0)
{
#ifdef DEBUG
printf("'logfile' line found. Trying to set logfile...");
#endif
curfolder->logfile=strdup(tmpc2);
#ifdef DEBUG
printf("set to %s!\n",curfolder->logfile);
#endif
}
free(tmpc1);
free(tmpc2);
}
void defraglst()
{
// FOO BAR
}
void parsecommon(inifile *i)
{
char *s=NULL;
while ((s=ini_nextline(i))!=NULL)
{
if (strchr(s, '=')!=NULL)
parseequal(s);
free(s);
}
}
void dumpfolders()
{
element *c=head(folders);
while (c->next!=NULL)
{
c=c->next;
curfolder=(Twatchfolder *)c->cur;
if (c->cur!=NULL)
printf("Will watch dir '%s' for files and run '%s' on them with uid='%d' flags: %d\n",curfolder->dir, curfolder->runprg, curfolder->user, curfolder->flags);
}
}
void consolidate()
{
element *c=head(folders);
while (c->next!=NULL)
{
c=c->next;
curfolder=(Twatchfolder *)c->cur;
if (((Twatchfolder *)c->cur)->dir==NULL)
{
folders=unchain(c);
c=head(folders);
}
}
}
void init()
{
inifile *f;
char *buf;
int i;
tmpdir=malloc(1024);
strcpy(tmpdir,"/tmp/");
mvprg=malloc(1024);
strcpy(mvprg,"/bin/mv");
rmprg=malloc(1024);
strcpy(rmprg,"/bin/rm");
cpprg=malloc(1024);
strcpy(cpprg,"/bin/cp");
timeofgrace=30;
defaultfolder.flags=(FL_ACT_EXISTENCE | FL_ACT_COPY | FL_ACT_DELETE);
defaultfolder.gracetime=timeofgrace;
defaultfolder.logfile=strdup("/dev/stdout");
folders=newchain();
buf=malloc(1024);
f=ini_open(config_file);
if (!f)
{
printf("Error: Could not open /usr/local/etc/watchd.conf (%s) , exiting..\n", strerror(errno));
free (defaultfolder.logfile);
free (buf);
free (cpprg);
free (rmprg);
free (mvprg);
free (tmpdir);
clearlst (folders);
exit(1);
}
#ifdef DEBUG
printf("Stage 1 : Read common options..\n");
#endif
i=ini_goto_grp(f, "common");
if (i!=-1)
{
parsecommon(f);
}
#ifdef DEBUG
else printf("Waring: could't read common options\n");
#endif
#ifdef DEBUG
printf("Stage 2 : Read folders..\n");
#endif
ini_rewind(f);
while ((ini_nextgrp(f)!=-1))
{
addfolder(NULL, NULL, NULL, -1, -1, 0);
if (strcasecmp(f->sts.cur_grp,"common")!=0)
while ((buf=ini_nextline(f))!=NULL)
{
if (strstr(buf,"=")!=NULL)
parseequal(buf);
free(buf);
}
}
ini_close(f);
consolidate();
#ifdef DEBUG
dumpfolders();
#endif
}
void deinit()
{
element *e=head(folders);
Twatchfolder *w;
#ifdef DEBUG
printf("deinit");
#endif
while (e!=NULL)
{
#ifdef DEBUG
printf(".");
#endif
w=(Twatchfolder *)e->cur;
if (w!=NULL)
{
free(w->runprg);
free(w->dir);
killfilechain(w->filechain);
}
e=unchain(e);
}
#ifdef DEBUG
printf("deinited\n");
#endif
}
int run(char* cmd, char* arg1, char* arg2, char* arg3, char* arg4, uid_t uid, int logfd)
{
char **args;
int f;
int r;
#ifdef DEBUG
printf("run(cmd=%s,arg1=%s,arg2=%s,arg3=%s,arg4=%s)\n",cmd, arg1, arg2, arg3, arg4);
#endif
f=fork();
if (f==0) {
if (logfd>0)
{
dup2(logfd, STDOUT_FILENO);
dup2(STDOUT_FILENO , STDERR_FILENO);
close(logfd);
}
args=malloc(sizeof(args)*5); // allocate ram for 4 arguments...
args[0]=strdup(cmd);
args[1]=arg1!=NULL?strdup(arg1):NULL;
args[2]=arg2!=NULL?strdup(arg2):NULL;
args[3]=arg3!=NULL?strdup(arg3):NULL;
args[4]=arg4!=NULL?strdup(arg4):NULL;
deinit();
cmd=args[0];
#ifdef DEBUG
fprintf(stderr,"forked of, uid=%d...\n", uid);
#endif
if (uid!=-1)
{
#ifdef DEBUG
fprintf(stderr, "Doing setuid(%d)...",uid);
#endif
if (setuid(uid)!=0)
{
#ifdef DEBUG
printf("failed!\n");
#endif
syslog(LOG_WARNING||LOG_DAEMON,"Could not setuid(%d): %s", uid, strerror(errno));
}
#ifdef DEBUG
printf("ok..\n");
#endif
}
#ifdef DEBUG
printf("Executing %s...\n", cmd);
#endif
execvp(cmd,args);
printf("Something went wrong! Could not execute %s... (%s) ",cmd, strerror(errno));
syslog(LOG_WARNING||LOG_DAEMON,"Could not execute %s",cmd);
exit(0); // Should never occur, but we are paranoid...
} else
{
#ifdef DEBUG
printf("pid of child=%d\n",f);
#endif
waitpid(f,&r,0); // wait for process to finish..
#ifdef DEBUG
printf("child died: pid=%d return=%d\n", f, r);
#endif
}
return(r);
}
char *prepare_logfd(Twatchfolder *folder, int *fd)
{
int r=-1;
char *name=strdup("/tmp/watchdrun.XXXXXX");
if ((folder->flags & (FL_LOG_SYSLOG|FL_LOG_FILE|FL_LOG_MAIL))!=0)
{
r=mkstemp(name);
}
*fd=r;
return(name);
}
void finalize_logfd(int fd, int r, Twatchfolder *folder, char *logname)
{
int log,l;
char buf[512];
FILE *f;
#ifdef DEBUG
printf("Entering finalize_logfd(fd=%i, r=%d, folder, logname='%s');\n", fd, r, logname);
#endif
if (fd>0)
{
if ((folder->flags&FL_LOG_ALWAYS) | (r!=0))
{
if (folder->flags&FL_LOG_FILE)
{
log=open(curfolder->logfile, O_CREAT | O_WRONLY | O_APPEND );
if (log>0)
{
lseek(fd, SEEK_SET, 0); // Rewind temp log file..
snprintf(buf, 511, "Program %s returned %d, stdout/err follows:\n", folder->runprg, r);
write(log, buf, strlen(buf));
l=222222;
while (l>0)
{
l=read(fd, buf, 512);
write(log, buf, l);
}
if (close(fd)) // Close the tempfile
{
#ifdef DEBUG
printf("Error closing fd=%d!\n",fd);
#endif
}
fd=0;
close(log);
}
}
if (folder->flags&FL_LOG_SYSLOG)
{
lseek(fd, SEEK_SET, 0); // Rewind temp log file..
snprintf(buf, 511, "Program %s returned %d, stdout/err follows:\n", folder->runprg, r);
openlog(folder->runprg, LOG_PID, LOG_DAEMON);
f=fdopen(fd, "r");
while (!feof(f))
{
if (fgets(buf, 511, f)!=NULL)
syslog((r==0)?LOG_NOTICE:LOG_WARNING, "%s\n", buf);
}
closelog();
fclose(f);
if (close(fd)) // Close the tempfile
{
#ifdef DEBUG
printf("Error closing fd=%d!\n",fd);
#endif
}
fd=0;
}
}
if (fd!=0)
close(fd);
unlink(logname); // Kill it!
}
}
//void processfile(char *file, char *srcdir, char *cmd, uid_t uid)
int processfile(char *file, Twatchfolder *folder)
{
char *complete;
char *srcfile;
char *logname;
struct timeval tp;
Twatchfolder w;
int logfd,r;
memcpy(&w,folder,sizeof(w));
#ifdef DEBUG
printf("processfile called! (folder=%s, flags=%d\n)\n", w.dir, w.flags);
#endif
complete=malloc(1024);
srcfile=malloc(1024);
strcpy(srcfile,w.dir);
strcat(srcfile,"/");
strcat(srcfile,file);
#ifdef DEBUG
printf("calling checkfile...\n");
#endif
if ((w.flags & FL_ACT_CHANGE)!=0)
if (checkfile(srcfile, 0, w.filechain)!=2)
{
// Happens in case a folder should be watched for changes
// an there is no demand of processing
free(complete);
free(srcfile);
return(0);
}
if ((r=get_filestate(srcfile))==1) {
syslog(LOG_NOTICE||LOG_DAEMON,"processing %s with %s",file,w.runprg);
if ((w.flags & FL_ACT_COPY)!=0)
{
gettimeofday(&tp,NULL);
sprintf(complete, "%s/watchdtmp.%d%d", tmpdir, tp.tv_sec, tp.tv_usec);
mkdir(complete,0700);
#ifdef DEBUG
printf("created tempdir : %s\n",complete);
#endif
chown(complete, w.user, 0);
#ifdef DEBUG
printf("doing chown(%d, %d)", w.user, 0);
#endif
#ifdef DEBUG
printf("running cp...\n");
#endif
run(cpprg,srcfile,complete, NULL, NULL, w.user, -1);
if ((w.flags & FL_ACT_DELETE)!=0)
{
#ifdef DEBUG
printf("running rm...\n");
#endif
run(rmprg, srcfile, NULL, NULL, NULL, w.user, -1);
}
strcpy(srcfile,complete);
strcat(srcfile,"/");
strcat(srcfile,file);
}
logname=prepare_logfd(folder, &logfd);
r=run(w.runprg, srcfile, NULL, NULL, NULL, w.user, logfd);
finalize_logfd(logfd, r, folder, logname);
if ((w.flags & FL_ACT_COPY) == FL_ACT_COPY)
run(rmprg,"-rf",complete, NULL, NULL, w.user, -1);
if ((w.flags & FL_ACT_DELETE)!=0)
run(rmprg,"-rf",srcfile, NULL, NULL, w.user, -1);
} else {
if (r!=2)
syslog(LOG_NOTICE||LOG_DAEMON,"file %s is locked, not processed",file,w.runprg);
}
free(complete);
free(srcfile);
//printf("flushing...\n");
fflush(stdout);
fflush(stdin);
fflush(stderr);
return 0;
}
int isdir(char *name)
{
struct stat st;
#ifdef DEBUG
printf("isdir(%s)=", name);
#endif
stat(name, &st);
if (S_ISDIR(st.st_mode)) {
#ifdef DEBUG
printf("1\n");
#endif
return(1);
} else {
#ifdef DEBUG
printf("0\n");
#endif
return(0);
}
}
void processdir(Twatchfolder *f)
{
DIR* dir=NULL;
struct dirent *de;
char* tmpS;
// int child;
#ifdef DEBUG
printf("Processing dir '%s' prg='%s' uid='%d' interval='%d' curcount='%d'...\n", f->dir, f->runprg, f->user, f->interval, f->curcount);
#endif
if (f->dir!=NULL) dir=opendir(f->dir); else dir=NULL;
tmpS=malloc(1024);
if (dir!=NULL)
{
de=readdir(dir);
while (de!=NULL)
{
if ((strcmp(de->d_name,".")!=0) && (strcmp(de->d_name,"..")!=0)) { // we don't process dirs, yet...
#ifdef DEBUG
printf("Processing '%s' (de->d_name).\n ", de->d_name);
#endif
processfile(de->d_name,f);
// if (isdir(de->d_name)==0) {
#ifdef DEBUG
printf("Entering processdir()...");
#endif
// processfile(de->d_name,f->dir,f->runprg, f->user);
/* } else
{
syslog(LOG_WARNING||LOG_DAEMON,"sorry, cannot read dirs, currently ",folders[n].dir);
printf("Found a dir!\n");
//processdir(de->d_name);
} */
}
de=readdir(dir);
}
closedir(dir);
} else
{ printf("Warning: Could not read directory '%s'\n",f->dir);
syslog(LOG_WARNING||LOG_DAEMON,"could not read directory %s",f->dir);
}
free(tmpS);
}
void sighandler(int signal)
{
if (signal==SIGHUP)
{
init(); // Reread config file!
}
}
int process_args (int argc, char *argv[])
{
if (argc < 2)
{
printf ("Usage: %s [config file]\n", argv[0]);
config_file = default_config_filename;
printf ("Using config file: %s\n", config_file);
// exit (1);
}
else
{
config_file = argv[1];
printf ("Using config file: %s\n", config_file);
}
return 0;
}
int main(int argc, char *argv[])
{
// int i;
element *c;
process_args (argc, argv);
// signal(SIGHUP, sighandler);
init();
#ifndef DEBUG
if (fork()==0) {
#endif
while (1)
{
c=head(folders);
while (c->next!=NULL)
{
c=c->next;
if (c->cur!=NULL)
{
if (((Twatchfolder *)c->cur)->gracetime--<1)
{
#ifdef DEBUG
printf("scanchain()\n");
#endif
((Twatchfolder *)c->cur)->filechain=scanchain(((Twatchfolder *)c->cur)->filechain);
((Twatchfolder *)c->cur)->gracetime=timeofgrace;
}
if (((Twatchfolder *)c->cur)->curcount--<1)
{
processdir((Twatchfolder *)c->cur);
((Twatchfolder *)c->cur)->curcount=((Twatchfolder *)c->cur)->interval-1;
}
}
}
sleep(interval);
}
#ifndef DEBUG
}
return(0);
#endif
}
syntax highlighted by Code2HTML, v. 0.9.1