1 /*
2 ** Copyright 2002-2021 Double Precision, Inc.
3 ** See COPYING for distribution information.
4 */
5 
6 #include "config.h"
7 #include "maildirwatch.h"
8 
9 #include <unistd.h>
10 #include <string.h>
11 #include <limits.h>
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <errno.h>
15 #include <signal.h>
16 #include <sys/signal.h>
17 #if HAVE_INOTIFY_INIT
18 #include <sys/inotify.h>
19 #include <poll.h>
20 #include <fcntl.h>
21 #endif
22 
23 #ifndef PATH_MAX
24 #define PATH_MAX 4096
25 #endif
26 
maildirwatch_alloc(const char * maildir)27 struct maildirwatch *maildirwatch_alloc(const char *maildir)
28 {
29 	char wd[PATH_MAX];
30 	struct maildirwatch *w;
31 
32 	if (maildir == 0 || *maildir == 0)
33 		maildir=".";
34 
35 	if (getcwd(wd, sizeof(wd)-1) == NULL)
36 		return NULL;
37 
38 	if (*maildir == '/')
39 		wd[0]=0;
40 	else
41 		strcat(wd, "/");
42 
43 	if ((w=malloc(sizeof(struct maildirwatch))) == NULL)
44 		return NULL;
45 
46 	if ((w->maildir=malloc(strlen(wd)+strlen(maildir)+1)) == NULL)
47 	{
48 		free(w);
49 		return NULL;
50 	}
51 
52 	strcat(strcpy(w->maildir, wd), maildir);
53 
54 #if HAVE_INOTIFY_INIT
55 #if HAVE_INOTIFY_INIT1
56 #ifdef IN_CLOEXEC
57 #else
58 #undef HAVE_INOTIFY_INIT1
59 #endif
60 #ifdef IN_NONBLOCK
61 #else
62 #undef HAVE_INOTIFY_INIT1
63 #endif
64 #endif
65 
66 #if HAVE_INOTIFY_INIT1
67 	w->inotify_fd=inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
68 #else
69 	w->inotify_fd=inotify_init();
70 
71 	if (w->inotify_fd >= 0 &&
72 	    (fcntl(w->inotify_fd, F_SETFL, O_NONBLOCK) < 0 ||
73 	     fcntl(w->inotify_fd, F_SETFD, FD_CLOEXEC)))
74 	{
75 		close(w->inotify_fd);
76 		w->inotify_fd=-1;
77 	}
78 #endif
79 
80 	if (w->inotify_fd < 0)
81 	{
82 		maildirwatch_free(w);
83 		w=NULL;
84 	}
85 #endif
86 	return w;
87 }
88 
maildirwatch_free(struct maildirwatch * w)89 void maildirwatch_free(struct maildirwatch *w)
90 {
91 #if HAVE_INOTIFY_INIT
92 	if (w->inotify_fd >= 0)
93 	{
94 		close(w->inotify_fd);
95 	}
96 #endif
97 
98 	free(w->maildir);
99 	free(w);
100 }
101 
maildirwatch_cleanup()102 void maildirwatch_cleanup()
103 {
104 }
105 
106 #if HAVE_INOTIFY_INIT
107 
108 /*
109 ** Poll the inotify file descriptor. Returns 0 on timeout, non-0 if
110 ** the inotify file descriptor becomes ready before the timeout expires.
111 */
112 
poll_inotify(struct maildirwatch * w)113 static int poll_inotify(struct maildirwatch *w)
114 {
115 	time_t now2;
116 
117 	int rc;
118 
119 	struct pollfd pfd;
120 
121 	while (w->now < w->timeout)
122 	{
123 		pfd.fd=w->inotify_fd;
124 		pfd.events=POLLIN;
125 
126 		rc=poll(&pfd, 1, (w->timeout - w->now)*1000);
127 
128 		now2=time(NULL);
129 
130 		if (now2 < w->now)
131 			return 1; /* System clock changed */
132 
133 		w->now=now2;
134 
135 		if (rc && pfd.revents & POLLIN)
136 			return 1;
137 	}
138 
139 	return 0;
140 }
141 
142 /*
143 ** read() inotify_events from the inotify handler.
144 */
145 
read_inotify_events(int fd,void (* callback)(struct inotify_event * ie,void * arg),void * arg)146 static int read_inotify_events(int fd,
147 			       void (*callback)(struct inotify_event *ie,
148 						void *arg),
149 			       void *arg)
150 {
151 	char inotify_buffer[sizeof(struct inotify_event)+NAME_MAX+1];
152 	int l;
153 	char *iecp;
154 
155 	l=read(fd, inotify_buffer, sizeof(inotify_buffer));
156 
157 	if (l < 0 &&
158 	    (errno == EAGAIN || errno == EWOULDBLOCK))
159 		l=0; /* Non-blocking socket timeout */
160 
161 	if (l < 0)
162 	{
163 		fprintf(stderr, "ERR:inotify read: %s\n", strerror(errno));
164 		return -1;
165 	}
166 
167 	iecp=inotify_buffer;
168 
169 	while (iecp < inotify_buffer+l)
170 	{
171 		struct inotify_event *ie=
172 			(struct inotify_event *)iecp;
173 
174 		iecp += sizeof(struct inotify_event)+ie->len;
175 
176 		(*callback)(ie, arg);
177 	}
178 	return 0;
179 }
180 
181 struct unlock_info {
182 	int handle;
183 	int removed;
184 	int deleted;
185 };
186 
unlock_handler(struct inotify_event * ie,void * arg)187 static void unlock_handler(struct inotify_event *ie,
188 			   void *arg)
189 {
190 	struct unlock_info *ui=(struct unlock_info *)arg;
191 
192 	if (ie->wd == ui->handle)
193 	{
194 		if (ie->mask & IN_DELETE_SELF)
195 			ui->removed=1;
196 
197 		if (ie->mask & IN_IGNORED)
198 			ui->deleted=1;
199 	}
200 }
201 #endif
202 
do_maildirwatch_unlock(struct maildirwatch * w,int nseconds,const char * p)203 static int do_maildirwatch_unlock(struct maildirwatch *w, int nseconds,
204 				  const char *p)
205 {
206 #if HAVE_INOTIFY_INIT
207 	int cancelled=0;
208 
209 	struct unlock_info ui;
210 
211 	ui.handle=inotify_add_watch(w->inotify_fd, p, IN_DELETE_SELF);
212 	ui.removed=0;
213 	ui.deleted=0;
214 
215 	if (ui.handle < 0)
216 	{
217 		if (errno == ENOENT)
218 		{
219 			/* Doesn't exist anymore, that's ok */
220 			return 0;
221 		}
222 
223 		fprintf(stderr, "ERR: %s: %s\n", p, strerror(errno));
224 		return -1;
225 	}
226 
227 	if (nseconds < 0)
228 		nseconds=0;
229 
230 	time(&w->now);
231 
232 	w->timeout=w->now + nseconds;
233 
234 	do
235 	{
236 		errno=ETIMEDOUT;
237 
238 		if (!poll_inotify(w))
239 		{
240 			if (!cancelled)
241 			{
242 				/*
243 				** Timeout on the lock, cancel the inotify.
244 				*/
245 				w->timeout=w->now+15;
246 				cancelled=1;
247 				inotify_rm_watch(w->inotify_fd, ui.handle);
248 				continue;
249 			}
250 
251 			fprintf(stderr, "ERR:inotify timeout: %s\n",
252 				strerror(errno));
253 
254 			break;
255 		}
256 
257 		read_inotify_events(w->inotify_fd, unlock_handler, &ui);
258 
259 		if (ui.removed && !cancelled)
260 		{
261 			w->timeout=w->now+15;
262 			cancelled=1;
263 			inotify_rm_watch(w->inotify_fd, ui.handle);
264 		}
265 
266 		/* We don't terminate the loop until we get IN_IGNORED */
267 
268 	} while (!ui.deleted);
269 
270 	return ui.removed;
271 #else
272 
273 	int n;
274 
275 	for (n=0; n<nseconds; ++n)
276 	{
277 		if (access(p, 0))
278 			return 1;
279 		sleep(1);
280 	}
281 	return 0;
282 #endif
283 }
284 
maildirwatch_unlock(struct maildirwatch * w,int nseconds)285 int maildirwatch_unlock(struct maildirwatch *w, int nseconds)
286 {
287 	char *p;
288 	int rc;
289 
290 	p=malloc(strlen(w->maildir)+ sizeof("/" WATCHDOTLOCK));
291 
292 	if (!p)
293 		return -1;
294 
295 	strcat(strcpy(p, w->maildir), "/" WATCHDOTLOCK);
296 
297 	rc=do_maildirwatch_unlock(w, nseconds, p);
298 
299 	free(p);
300 	return rc;
301 }
302 
maildirwatch_start(struct maildirwatch * w,struct maildirwatch_contents * mc)303 int maildirwatch_start(struct maildirwatch *w,
304 		       struct maildirwatch_contents *mc)
305 {
306 	mc->w=w;
307 
308 	time(&w->now);
309 	w->timeout = w->now + 60;
310 
311 #if HAVE_INOTIFY_INIT
312 
313 	{
314 		char *s=malloc(strlen(w->maildir)
315 			       +sizeof("/" KEYWORDDIR));
316 
317 		if (!s)
318 			return (-1);
319 
320 		strcat(strcpy(s, w->maildir), "/new");
321 
322 		mc->handles[0]=inotify_add_watch(w->inotify_fd, s,
323 						 IN_CREATE |
324 						 IN_DELETE |
325 						 IN_MOVED_FROM |
326 						 IN_MOVED_TO);
327 
328 
329 		strcat(strcpy(s, w->maildir), "/cur");
330 
331 		mc->handles[1]=inotify_add_watch(w->inotify_fd, s,
332 						 IN_CREATE |
333 						 IN_DELETE |
334 						 IN_MOVED_FROM |
335 						 IN_MOVED_TO);
336 
337 		strcat(strcpy(s, w->maildir), "/" KEYWORDDIR);
338 
339 		mc->handles[2]=inotify_add_watch(w->inotify_fd, s,
340 						 IN_CREATE |
341 						 IN_DELETE |
342 						 IN_MOVED_FROM |
343 						 IN_MOVED_TO);
344 		free(s);
345 	}
346 
347 	return 0;
348 #else
349 	return 1;
350 #endif
351 }
352 
maildirwatch_started(struct maildirwatch_contents * mc,int * fdret)353 int maildirwatch_started(struct maildirwatch_contents *mc,
354 			 int *fdret)
355 {
356 #if HAVE_INOTIFY_INIT
357 	int n;
358 #endif
359 
360 	*fdret= -1;
361 
362 #if HAVE_INOTIFY_INIT
363 
364 	for (n=0; n<sizeof(mc->handles)/sizeof(mc->handles[0]); ++n)
365 	{
366 		if (mc->handles[n] < 0)
367 			return -1;
368 	}
369 
370 	*fdret=mc->w->inotify_fd;
371 
372 	return 1;
373 
374 #else
375 	*fdret= -1;
376 
377 	return 1;
378 #endif
379 }
380 
381 #if HAVE_INOTIFY_INIT
382 
383 struct check_info {
384 	struct maildirwatch_contents *mc;
385 	int *changed;
386 	int handled;
387 };
388 
check_handler(struct inotify_event * ie,void * arg)389 static void check_handler(struct inotify_event *ie,
390 			  void *arg)
391 {
392 	struct check_info *ci=(struct check_info *)arg;
393 	int n;
394 
395 	ci->handled=1;
396 
397 	for (n=0; n<sizeof(ci->mc->handles)/sizeof(ci->mc->handles[0]); ++n)
398 	{
399 		if (ie->wd == ci->mc->handles[n])
400 			*ci->changed=1;
401 	}
402 
403 }
404 #endif
405 
maildirwatch_check(struct maildirwatch_contents * mc,int * changed,int * fdret,int * timeout)406 int maildirwatch_check(struct maildirwatch_contents *mc,
407 		       int *changed,
408 		       int *fdret,
409 		       int *timeout)
410 {
411 	struct maildirwatch *w=mc->w;
412 	time_t curTime;
413 #if HAVE_INOTIFY_INIT
414 	struct check_info ci;
415 
416 	ci.mc=mc;
417 	ci.changed=changed;
418 #endif
419 
420 	*changed=0;
421 	*fdret=-1;
422 
423 	curTime=time(NULL);
424 
425 	if (curTime < w->now)
426 		w->timeout=curTime; /* System clock changed */
427 	w->now=curTime;
428 
429 	*timeout=60;
430 
431 #if HAVE_INOTIFY_INIT
432 	if (maildirwatch_started(mc, fdret) > 0)
433 	{
434 		*timeout=60 * 60;
435 
436 		*fdret=w->inotify_fd;
437 
438 		ci.handled=1;
439 
440 		while (ci.handled)
441 		{
442 			ci.handled=0;
443 			read_inotify_events(w->inotify_fd, check_handler, &ci);
444 		}
445 	}
446 #endif
447 	if (w->now >= w->timeout)
448 	{
449                 w->timeout = w->now + *timeout;
450 		*changed=1;
451 	}
452 	return 0;
453 }
454 
455 #if HAVE_INOTIFY_INIT
456 
457 struct end_info {
458 	struct maildirwatch_contents *mc;
459 	int unwatched;
460 };
461 
end_handler(struct inotify_event * ie,void * arg)462 static void end_handler(struct inotify_event *ie,
463 			void *arg)
464 {
465 	struct end_info *ei=(struct end_info *)arg;
466 	int n;
467 
468 	for (n=0; n<sizeof(ei->mc->handles)/sizeof(ei->mc->handles[0]); ++n)
469 	{
470 		if (ie->wd == ei->mc->handles[n] &&
471 		    ie->mask & IN_IGNORED)
472 		{
473 			ei->mc->handles[n]=-1;
474 			ei->unwatched=1;
475 		}
476 	}
477 }
478 
maildir_end_unwatch(struct maildirwatch_contents * mc)479 static int maildir_end_unwatch(struct maildirwatch_contents *mc)
480 {
481 	int n;
482 
483 	for (n=0; n<sizeof(mc->handles)/sizeof(mc->handles[0]); ++n)
484 	{
485 		if (mc->handles[n] >= 0)
486 		{
487 			inotify_rm_watch(mc->w->inotify_fd,
488 					 mc->handles[n]);
489 			return 1;
490 		}
491 	}
492 	return 0;
493 }
494 #endif
495 
maildirwatch_end(struct maildirwatch_contents * mc)496 void maildirwatch_end(struct maildirwatch_contents *mc)
497 {
498 #if HAVE_INOTIFY_INIT
499 	struct end_info ei;
500 
501 	time(&mc->w->now);
502 	mc->w->timeout=mc->w->now + 15;
503 
504 	if (maildir_end_unwatch(mc)) /* Send the first inotify_rm_watch */
505 	{
506 		while (1)
507 		{
508 			if (poll_inotify(mc->w) != 1)
509 			{
510 				fprintf(stderr, "ERR:inotify timeout: %s\n",
511 					strerror(errno));
512 				break;
513 			}
514 
515 			ei.mc=mc;
516 			ei.unwatched=0;
517 			read_inotify_events(mc->w->inotify_fd,
518 						end_handler, &ei);
519 
520 			if (ei.unwatched)
521 			{
522 				/* Send the next inotify_rm_watch? */
523 				if (!maildir_end_unwatch(mc))
524 					break; /* Nope, all done! */
525 			}
526 		}
527 	}
528 #endif
529 }
530