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