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