ftrigger.c (5900B) download
1#include <sys/types.h>
2#include <unistd.h>
3#include <sys/stat.h>
4#include <fcntl.h>
5#include <sys/inotify.h>
6#include <stdlib.h>
7#include <string.h>
8#include <buffer.h>
9#include <errno.h>
10#include <sys/poll.h>
11#include <sys/wait.h>
12#include <sys/signal.h>
13
14struct trigger {
15 const char* filename,* command;
16 char* dironly,* fileonly;
17 struct stat ss;
18 int idd,idf,created; /* inotify-deskriptor */
19 /* created = 1: got a create event on the dir, added a watch on the file, but no modify events on the file yet */
20}* root;
21int n;
22
23void sighandler(int signum) {
24 int status;
25 (void)signum;
26 wait(&status);
27}
28
29int v;
30
31int main(int argc,char* argv[]) {
32 int i,in;
33 const char* command="make";
34 struct stat ss;
35 char buf[2048];
36 struct inotify_event* ie=(struct inotify_event*)buf;
37 struct pollfd p;
38 static struct sigaction sa;
39
40 sa.sa_flags=SA_RESTART|SA_NOCLDSTOP;
41 sa.sa_handler=SIG_IGN;
42 sigaction(SIGCHLD,&sa,0);
43 in=inotify_init();
44 root=(struct trigger*)alloca(sizeof(struct trigger)*argc);
45 memset(root,0,sizeof(struct trigger)*argc);
46 for (i=1; i<argc; ++i) {
47 if (!strcmp(argv[i],"-v")) {
48 v=1;
49 continue;
50 }
51 if (argv[i][0]=='@') {
52 command=argv[i]+1;
53 ++i;
54 }
55 if (argv[i]) {
56 char* c;
57 root[n].filename=argv[i];
58 root[n].command=command;
59 root[n].dironly=alloca(strlen(root[n].filename)+3);
60 c=strrchr(root[n].filename,'/');
61 if (c) {
62 size_t m=c-root[n].filename;
63 strcpy(root[n].dironly,root[n].filename);
64 root[n].fileonly=root[n].dironly+m+1;
65 root[n].fileonly[-1]=0;
66 } else {
67 root[n].dironly[0]='.';
68 root[n].dironly[1]=0;
69 root[n].fileonly=root[n].dironly+2;
70 strcpy(root[n].fileonly,root[n].filename);
71 }
72 ++n;
73 }
74 }
75
76 for (i=0; i<n; ++i) {
77 root[i].idf=-1;
78 if (stat(root[i].filename,&root[i].ss)!=0)
79 buffer_putmflush(buffer_2,"warning: could not stat file \"",root[i].filename,"\": ",strerror(errno),"\n");
80 else
81 root[i].idf=inotify_add_watch(in,root[i].filename,
82 IN_CLOSE_WRITE|IN_MOVE_SELF|IN_DELETE_SELF|IN_MODIFY);
83 root[i].idd=inotify_add_watch(in,root[i].dironly,
84 IN_MOVED_TO|IN_CREATE);
85 root[i].created=0;
86 }
87
88 p.fd=in;
89 p.events=POLLIN;
90
91 for (;;) {
92again:
93 switch (poll(&p,1,1000)) {
94 case -1:
95 if (errno==EINTR) {
96 int status;
97 wait(&status);
98 break;
99 }
100 return 1;
101 case 0:
102 {
103 int foundone=0;
104 for (i=0; i<n; ++i)
105 if (root[i].created==1) {
106 memset(&root[i].ss,0,sizeof(root[i].ss));
107 root[i].created=0;
108 foundone=1;
109 }
110 if (foundone) goto goodevent;
111 }
112 continue;
113 case 1: break;
114 };
115 if (read(in,buf,sizeof(buf)) < (ssize_t)sizeof(*ie)) {
116 buffer_putmflush(buffer_2,"error reading inotify notification: ",strerror(errno),"\n");
117 exit(111);
118 }
119
120#if 0
121 buffer_puts(buffer_1,"got event for wd ");
122 buffer_putulong(buffer_1,ie->wd);
123 if (ie->len)
124 buffer_putmflush(buffer_1," with filename \"",ie->name,"\"\n");
125 else
126 buffer_putsflush(buffer_1," with no associated filename\n");
127 struct {
128 unsigned int mask;
129 const char* string;
130 } strings[] = {
131 { 0x00000001,"IN_ACCESS" },
132 { 0x00000002,"IN_MODIFY" },
133 { 0x00000004,"IN_ATTRIB" },
134 { 0x00000008,"IN_CLOSE_WRITE" },
135 { 0x00000010,"IN_CLOSE_NOWRITE" },
136 { 0x00000020,"IN_OPEN" },
137 { 0x00000040,"IN_MOVED_FROM" },
138 { 0x00000080,"IN_MOVED_TO" },
139 { 0x00000100,"IN_CREATE" },
140 { 0x00000200,"IN_DELETE" },
141 { 0x00000400,"IN_DELETE_SELF" },
142 { 0x00000800,"IN_MOVE_SELF" },
143 { 0x00002000,"IN_UNMOUNT" },
144 { 0x00004000,"IN_Q_OVERFLOW" },
145 { 0x00008000,"IN_IGNORED" },
146 };
147
148 for (i=0; i<(int)(sizeof(strings)/sizeof(strings[0])); ++i) {
149 if (ie->mask&strings[i].mask)
150 buffer_putm(buffer_1," ",strings[i].string);
151 }
152 buffer_putnlflush(buffer_1);
153
154#endif
155
156 for (i=0; i<n; ++i) {
157 if (root[i].idf==ie->wd) {
158 if (ie->mask & IN_MODIFY) {
159 root[i].created=0;
160 root[i].idf=inotify_add_watch(in,root[i].filename,
161 IN_CLOSE_WRITE|IN_MOVE_SELF|IN_DELETE_SELF);
162 continue;
163 }
164 if (ie->mask & (IN_DELETE_SELF|IN_MOVE_SELF)) {
165#if 0
166 buffer_putmflush(buffer_1,root[i].filename," was ",
167 ie->mask&IN_DELETE_SELF ? "deleted" : "renamed",
168 ", cancelling subscription\n");
169#endif
170 inotify_rm_watch(in,ie->wd);
171 root[i].idf=-1;
172 goto again;
173 }
174 goto goodevent;
175 } else if (root[i].idd==ie->wd) {
176 if (!strcmp(ie->name,root[i].fileonly)) {
177#if 0
178 buffer_putmflush(buffer_1,"the file we were interested in, ",
179 root[i].filename,", has been created. Adding subscription.\n");
180#endif
181 if (root[i].idf!=-1)
182 inotify_rm_watch(in,root[i].idf);
183 root[i].idf=inotify_add_watch(in,root[i].filename,
184 IN_CLOSE_WRITE|IN_MOVE_SELF|IN_DELETE_SELF|IN_MODIFY);
185 root[i].created=1;
186 /* if the file was created, it will be empty now, wait for
187 * the IN_CLOSE_WRITE event. */
188 if (ie->mask & IN_CREATE) {
189#if 0
190 buffer_putmflush(buffer_1,"file was created, so it will be empty now. Skipping until we get a close event.\n");
191#endif
192 goto again;
193 }
194 goto goodevent;
195 /* if the file was moved, this is a genuine event we are
196 * interested in. fall through */
197 } else goto again;
198#if 0
199 buffer_putmflush(buffer_1,"(directory event for \"",root[i].filename,"\")\n");
200#endif
201 }
202 }
203#if 0
204 buffer_putmflush(buffer_1,"ignoring irrelevant event.\n");
205#endif
206 goto again;
207
208goodevent:
209#if 0
210 buffer_putmflush(buffer_1,"got through to stat\n");
211#endif
212 for (i=0; i<n; ++i) {
213 if (stat(root[i].filename,&ss)==0 && memcmp(&ss,&root[i].ss,sizeof(ss))) {
214 memcpy(&root[i].ss,&ss,sizeof(ss));
215 if (v)
216 buffer_putmflush(buffer_1,"file \"",root[i].filename,"\" changed, running command \"",root[i].command,"\"\n");
217 if (vfork()==0) {
218 exit(system(root[i].command));
219 }
220 }
221 }
222 }
223 return 0;
224}