1 /* direvent - directory content watcher daemon
2 Copyright (C) 2012-2016 Sergey Poznyakoff
3
4 Direvent is free software; you can redistribute it and/or modify it
5 under the terms of the GNU General Public License as published by the
6 Free Software Foundation; either version 3 of the License, or (at your
7 option) any later version.
8
9 Direvent is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along
15 with direvent. If not, see <http://www.gnu.org/licenses/>. */
16
17 #include "direvent.h"
18 #include <sys/event.h>
19 #include <fcntl.h>
20 #include <signal.h>
21 #include <dirent.h>
22 #include <sys/stat.h>
23
24 struct transtab sysev_transtab[] = {
25 { "DELETE", NOTE_DELETE },
26 { "WRITE", NOTE_WRITE },
27 { "EXTEND", NOTE_EXTEND },
28 { "ATTRIB", NOTE_ATTRIB },
29 { "LINK", NOTE_LINK },
30 { "RENAME", NOTE_RENAME },
31 { "REVOKE", NOTE_REVOKE },
32 { NULL }
33 };
34
35
36 static int kq;
37 static struct kevent *evtab;
38 static struct kevent *chtab;
39 static int chcnt;
40 static int chclosed = -1;
41
42 event_mask genev_xlat[] = {
43 { GENEV_CREATE, 0 },
44 { GENEV_WRITE, NOTE_WRITE|NOTE_EXTEND },
45 { GENEV_ATTRIB, NOTE_ATTRIB|NOTE_LINK },
46 { GENEV_DELETE, NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE },
47 { 0 }
48 };
49
50 void
sysev_init()51 sysev_init()
52 {
53 kq = kqueue();
54 if (kq == -1) {
55 diag(LOG_CRIT, "kqueue: %s", strerror(errno));
56 exit(1);
57 }
58 evtab = calloc(sysconf(_SC_OPEN_MAX), sizeof(evtab[0]));
59 chtab = calloc(sysconf(_SC_OPEN_MAX), sizeof(chtab[0]));
60 }
61
62 int
sysev_filemask(struct watchpoint * dp)63 sysev_filemask(struct watchpoint *dp)
64 {
65 struct handler *h;
66 handler_iterator_t itr;
67
68 for_each_handler(dp, itr, h) {
69 if (h->ev_mask.sys_mask)
70 return S_IFMT;
71 }
72 return 0;
73 }
74
75 int
sysev_add_watch(struct watchpoint * wpt,event_mask mask)76 sysev_add_watch(struct watchpoint *wpt, event_mask mask)
77 {
78 int wd = open(wpt->dirname, O_RDONLY);
79 if (wd >= 0) {
80 struct stat st;
81 int sysmask;
82
83 if (fstat(wd, &st)) {
84 close(wd);
85 return -1;
86 }
87 wpt->file_mode = st.st_mode;
88 wpt->file_ctime = st.st_ctime;
89 sysmask = mask.sys_mask | NOTE_DELETE;
90 if (S_ISDIR(st.st_mode) && mask.gen_mask & GENEV_CREATE)
91 sysmask |= NOTE_WRITE;
92 EV_SET(chtab + chcnt, wd, EVFILT_VNODE,
93 EV_ADD | EV_ENABLE | EV_CLEAR, sysmask,
94 0, wpt);
95 wd = chcnt++;
96 }
97 return wd;
98 }
99
100 void
sysev_rm_watch(struct watchpoint * wpt)101 sysev_rm_watch(struct watchpoint *wpt)
102 {
103 close(chtab[wpt->wd].ident);
104 chtab[wpt->wd].ident = -1;
105 if (chclosed == -1 || chclosed > wpt->wd)
106 chclosed = wpt->wd;
107 }
108
109 static void
chclosed_elim()110 chclosed_elim()
111 {
112 int i, j;
113
114 if (chclosed == -1)
115 return;
116
117 for (i = chclosed, j = chclosed + 1; j < chcnt; j++)
118 if (chtab[j].ident != -1) {
119 struct watchpoint *wpt;
120
121 chtab[i] = chtab[j];
122 wpt = chtab[i].udata;
123 wpt->wd = i;
124 i++;
125 }
126 chcnt = i;
127 chclosed = -1;
128 }
129
130 static void
check_created(struct watchpoint * dp)131 check_created(struct watchpoint *dp)
132 {
133 DIR *dir;
134 struct dirent *ent;
135
136 dir = opendir(dp->dirname);
137 if (!dir) {
138 diag(LOG_ERR, "cannot open directory %s: %s",
139 dp->dirname, strerror(errno));
140 return;
141 }
142
143 while (ent = readdir(dir)) {
144 struct stat st;
145 char *pathname;
146
147 if (ent->d_name[0] == '.' &&
148 (ent->d_name[1] == 0 ||
149 (ent->d_name[1] == '.' && ent->d_name[2] == 0)))
150 continue;
151
152 if (watchpoint_pattern_match(dp, ent->d_name))
153 continue;
154
155 pathname = mkfilename(dp->dirname, ent->d_name);
156 if (!pathname) {
157 diag(LOG_ERR, "cannot stat %s/%s: not enough memory",
158 dp->dirname, ent->d_name);
159 continue;
160 }
161
162 if (stat(pathname, &st)) {
163 diag(LOG_ERR, "cannot stat %s: %s",
164 pathname, strerror(errno));
165 /* If ok, first see if the file is newer than the last
166 directory scan. If not, there is still a chance
167 the file is new (the timestamp precision leaves a
168 time window long enough for a file to be created)
169 so try the more expensive hash lookup to see if we
170 know about that file. If the file is new, register
171 a watcher for it. */
172 } else if (st.st_ctime > dp->file_ctime ||
173 !watchpoint_lookup(pathname)) {
174 deliver_ev_create(dp, dp->dirname, ent->d_name);
175 subwatcher_create(dp, pathname, 1);
176 dp->file_ctime = st.st_ctime;
177 }
178 free(pathname);
179 }
180 closedir(dir);
181 }
182
183 static void
process_event(struct kevent * ep)184 process_event(struct kevent *ep)
185 {
186 struct watchpoint *dp = ep->udata;
187 char *filename, *dirname;
188
189 if (!dp) {
190 diag(LOG_NOTICE, "unrecognized event %x", ep->fflags);
191 return;
192 }
193
194 ev_log(ep->fflags, dp);
195
196 if (S_ISDIR(dp->file_mode)
197 && !(ep->fflags & (NOTE_DELETE|NOTE_RENAME))) {
198 /* Check if new files have appeared. */
199 if (ep->fflags & NOTE_WRITE)
200 check_created(dp);
201 return;
202 }
203
204 filename = split_pathname(dp, &dirname);
205
206 watchpoint_run_handlers(dp, ep->fflags, dirname, filename);
207
208 unsplit_pathname(dp);
209
210 if (ep->fflags & (NOTE_DELETE|NOTE_RENAME)) {
211 debug(1, ("%s deleted", dp->dirname));
212 watchpoint_suspend(dp);
213 return;
214 }
215 }
216
217 int
sysev_select()218 sysev_select()
219 {
220 int i, n;
221
222 chclosed_elim();
223 n = kevent(kq, chtab, chcnt, evtab, chcnt, NULL);
224 if (n == -1) {
225 if (errno == EINTR) {
226 if (signo == 0 || signo == SIGCHLD || signo == SIGALRM)
227 return 0;
228 diag(LOG_NOTICE, "got signal %d", signo);
229 }
230 diag(LOG_ERR, "kevent: %s", strerror(errno));
231 return 1;
232 }
233
234 for (i = 0; i < n; i++)
235 process_event(&evtab[i]);
236
237 return 0;
238 }
239
240