mutt stable branch with some hacks
1/* * Copyright (C) 2018 Gero Treuner <gero@70t.de>
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 */
17
18#if HAVE_CONFIG_H
19# include "config.h"
20#endif
21
22#if HAVE_SYS_INOTIFY_H
23# include <sys/types.h>
24# include <sys/inotify.h>
25# include <unistd.h>
26# include <poll.h>
27#endif
28#ifndef HAVE_INOTIFY_INIT1
29# include <fcntl.h>
30#endif
31
32#include "mutt.h"
33#include "buffy.h"
34#include "monitor.h"
35#include "mx.h"
36#include "mutt_curses.h"
37
38#include <errno.h>
39#include <sys/stat.h>
40
41typedef struct monitor_t
42{
43 struct monitor_t *next;
44 char *mh_backup_path;
45 dev_t st_dev;
46 ino_t st_ino;
47 short magic;
48 int descr;
49}
50MONITOR;
51
52static int INotifyFd = -1;
53static MONITOR *Monitor = NULL;
54static size_t PollFdsCount = 0;
55static size_t PollFdsLen = 0;
56static struct pollfd *PollFds;
57
58static int MonitorContextDescriptor = -1;
59
60typedef struct monitorinfo_t
61{
62 short magic;
63 short isdir;
64 const char *path;
65 dev_t st_dev;
66 ino_t st_ino;
67 MONITOR *monitor;
68 BUFFER *_pathbuf; /* access via path only (maybe not initialized) */
69}
70MONITORINFO;
71
72#define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR)
73#define INOTIFY_MASK_FILE IN_CLOSE_WRITE
74
75static void mutt_poll_fd_add(int fd, short events)
76{
77 int i = 0;
78 for (i = 0; i < PollFdsCount && PollFds[i].fd != fd; ++i);
79
80 if (i == PollFdsCount)
81 {
82 if (PollFdsCount == PollFdsLen)
83 {
84 PollFdsLen += 2;
85 safe_realloc (&PollFds, PollFdsLen * sizeof(struct pollfd));
86 }
87 ++PollFdsCount;
88 PollFds[i].fd = fd;
89 PollFds[i].events = events;
90 }
91 else
92 PollFds[i].events |= events;
93}
94
95static int mutt_poll_fd_remove(int fd)
96{
97 int i = 0, d;
98 for (i = 0; i < PollFdsCount && PollFds[i].fd != fd; ++i);
99 if (i == PollFdsCount)
100 return -1;
101 d = PollFdsCount - i - 1;
102 if (d)
103 memmove (&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd));
104 --PollFdsCount;
105 return 0;
106}
107
108static int monitor_init ()
109{
110 if (INotifyFd == -1)
111 {
112#if HAVE_INOTIFY_INIT1
113 INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
114 if (INotifyFd == -1)
115 {
116 dprint (2, (debugfile, "monitor: inotify_init1 failed, errno=%d %s\n", errno, strerror(errno)));
117 return -1;
118 }
119#else
120 INotifyFd = inotify_init();
121 if (INotifyFd == -1)
122 {
123 dprint (2, (debugfile, "monitor: inotify_init failed, errno=%d %s\n", errno, strerror(errno)));
124 return -1;
125 }
126 fcntl(INotifyFd, F_SETFL, O_NONBLOCK);
127 fcntl(INotifyFd, F_SETFD, FD_CLOEXEC);
128#endif
129 mutt_poll_fd_add(0, POLLIN);
130 mutt_poll_fd_add(INotifyFd, POLLIN);
131 }
132 return 0;
133}
134
135static void monitor_check_free ()
136{
137 if (!Monitor && INotifyFd != -1)
138 {
139 mutt_poll_fd_remove(INotifyFd);
140 close (INotifyFd);
141 INotifyFd = -1;
142 MonitorFilesChanged = 0;
143 }
144}
145
146static MONITOR *monitor_create (MONITORINFO *info, int descriptor)
147{
148 MONITOR *monitor = (MONITOR *) safe_calloc (1, sizeof (MONITOR));
149 monitor->magic = info->magic;
150 monitor->st_dev = info->st_dev;
151 monitor->st_ino = info->st_ino;
152 monitor->descr = descriptor;
153 monitor->next = Monitor;
154 if (info->magic == MUTT_MH)
155 monitor->mh_backup_path = safe_strdup(info->path);
156
157 Monitor = monitor;
158
159 return monitor;
160}
161
162static void monitor_info_init (MONITORINFO *info)
163{
164 memset (info, 0, sizeof (MONITORINFO));
165}
166
167static void monitor_info_free (MONITORINFO *info)
168{
169 mutt_buffer_free (&info->_pathbuf);
170}
171
172static void monitor_delete (MONITOR *monitor)
173{
174 MONITOR **ptr = &Monitor;
175
176 if (!monitor)
177 return;
178
179 FOREVER
180 {
181 if (!*ptr)
182 return;
183 if (*ptr == monitor)
184 break;
185 ptr = &(*ptr)->next;
186 }
187
188 FREE (&monitor->mh_backup_path); /* __FREE_CHECKED__ */
189 monitor = monitor->next;
190 FREE (ptr); /* __FREE_CHECKED__ */
191 *ptr = monitor;
192}
193
194static int monitor_handle_ignore (int descr)
195{
196 int new_descr = -1;
197 MONITOR *iter = Monitor;
198 struct stat sb;
199
200 while (iter && iter->descr != descr)
201 iter = iter->next;
202
203 if (iter)
204 {
205 if (iter->magic == MUTT_MH && stat (iter->mh_backup_path, &sb) == 0)
206 {
207 if ((new_descr = inotify_add_watch (INotifyFd, iter->mh_backup_path, INOTIFY_MASK_FILE)) == -1)
208 dprint (2, (debugfile, "monitor: inotify_add_watch failed for '%s', errno=%d %s\n", iter->mh_backup_path, errno, strerror(errno)));
209 else
210 {
211 dprint (3, (debugfile, "monitor: inotify_add_watch descriptor=%d for '%s'\n", descr, iter->mh_backup_path));
212 iter->st_dev = sb.st_dev;
213 iter->st_ino = sb.st_ino;
214 iter->descr = new_descr;
215 }
216 }
217 else
218 {
219 dprint (3, (debugfile, "monitor: cleanup watch (implicitly removed) - descriptor=%d\n", descr));
220 }
221
222 if (MonitorContextDescriptor == descr)
223 MonitorContextDescriptor = new_descr;
224
225 if (new_descr == -1)
226 {
227 monitor_delete (iter);
228 monitor_check_free ();
229 }
230 }
231
232 return new_descr;
233}
234
235#define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1)
236
237/* mutt_monitor_poll: Waits for I/O ready file descriptors or signals.
238 *
239 * return values:
240 * -3 unknown/unexpected events: poll timeout / fds not handled by us
241 * -2 monitor detected changes, no STDIN input
242 * -1 error (see errno)
243 * 0 (1) input ready from STDIN, or (2) monitoring inactive -> no poll()
244 * MonitorFilesChanged also reflects changes to monitored files.
245 *
246 * Only STDIN and INotify file handles currently expected/supported.
247 * More would ask for common infrastructur (sockets?).
248 */
249int mutt_monitor_poll (void)
250{
251 int rc = 0, fds, i, inputReady;
252 char buf[EVENT_BUFLEN]
253 __attribute__ ((aligned(__alignof__(struct inotify_event))));
254
255 MonitorFilesChanged = 0;
256
257 if (INotifyFd != -1)
258 {
259 fds = poll (PollFds, PollFdsLen, MuttGetchTimeout);
260
261 if (fds == -1)
262 {
263 rc = -1;
264 if (errno != EINTR)
265 {
266 dprint (2, (debugfile, "monitor: poll() failed, errno=%d %s\n", errno, strerror(errno)));
267 }
268 }
269 else
270 {
271 inputReady = 0;
272 for (i = 0; fds && i < PollFdsCount; ++i)
273 {
274 if (PollFds[i].revents)
275 {
276 --fds;
277 if (PollFds[i].fd == 0)
278 {
279 inputReady = 1;
280 }
281 else if (PollFds[i].fd == INotifyFd)
282 {
283 MonitorFilesChanged = 1;
284 dprint (3, (debugfile, "monitor: file change(s) detected\n"));
285 int len;
286 char *ptr = buf;
287 const struct inotify_event *event;
288
289 FOREVER
290 {
291 len = read (INotifyFd, buf, sizeof(buf));
292 if (len == -1)
293 {
294 if (errno != EAGAIN)
295 dprint (2, (debugfile, "monitor: read inotify events failed, errno=%d %s\n",
296 errno, strerror(errno)));
297 break;
298 }
299
300 while (ptr < buf + len)
301 {
302 event = (const struct inotify_event *) ptr;
303 dprint (5, (debugfile, "monitor: + detail: descriptor=%d mask=0x%x\n",
304 event->wd, event->mask));
305 if (event->mask & IN_IGNORED)
306 monitor_handle_ignore (event->wd);
307 else if (event->wd == MonitorContextDescriptor)
308 MonitorContextChanged = 1;
309 ptr += sizeof(struct inotify_event) + event->len;
310 }
311 }
312 }
313 }
314 }
315 if (!inputReady)
316 rc = MonitorFilesChanged ? -2 : -3;
317 }
318 }
319
320 return rc;
321}
322
323#define RESOLVERES_OK_NOTEXISTING 0
324#define RESOLVERES_OK_EXISTING 1
325#define RESOLVERES_FAIL_NOMAILBOX -3
326#define RESOLVERES_FAIL_NOMAGIC -2
327#define RESOLVERES_FAIL_STAT -1
328
329/* monitor_resolve: resolve monitor entry match by BUFFY, or - if NULL - by Context.
330 *
331 * return values:
332 * >=0 mailbox is valid and locally accessible:
333 * 0: no monitor / 1: preexisting monitor
334 * -3 no mailbox (MONITORINFO: no fields set)
335 * -2 magic not set
336 * -1 stat() failed (see errno; MONITORINFO fields: magic, isdir, path)
337 */
338static int monitor_resolve (MONITORINFO *info, BUFFY *buffy)
339{
340 MONITOR *iter;
341 char *fmt = NULL;
342 struct stat sb;
343
344 if (buffy)
345 {
346 info->magic = buffy->magic;
347 info->path = buffy->realpath;
348 }
349 else if (Context)
350 {
351 info->magic = Context->magic;
352 info->path = Context->realpath;
353 }
354 else
355 {
356 return RESOLVERES_FAIL_NOMAILBOX;
357 }
358
359 if (!info->magic)
360 {
361 return RESOLVERES_FAIL_NOMAGIC;
362 }
363 else if (info->magic == MUTT_MAILDIR)
364 {
365 info->isdir = 1;
366 fmt = "%s/new";
367 }
368 else
369 {
370 info->isdir = 0;
371 if (info->magic == MUTT_MH)
372 fmt = "%s/.mh_sequences";
373 }
374
375 if (fmt)
376 {
377 if (!info->_pathbuf)
378 info->_pathbuf = mutt_buffer_new ();
379 mutt_buffer_printf (info->_pathbuf, fmt, info->path);
380 info->path = mutt_b2s (info->_pathbuf);
381 }
382 if (stat (info->path, &sb) != 0)
383 return RESOLVERES_FAIL_STAT;
384
385 iter = Monitor;
386 while (iter && (iter->st_ino != sb.st_ino || iter->st_dev != sb.st_dev))
387 iter = iter->next;
388
389 info->st_dev = sb.st_dev;
390 info->st_ino = sb.st_ino;
391 info->monitor = iter;
392
393 return iter ? RESOLVERES_OK_EXISTING : RESOLVERES_OK_NOTEXISTING;
394}
395
396/* mutt_monitor_add: add file monitor from BUFFY, or - if NULL - from Context.
397 *
398 * return values:
399 * 0 success: new or already existing monitor
400 * -1 failed: no mailbox, inaccessible file, create monitor/watcher failed
401 */
402int mutt_monitor_add (BUFFY *buffy)
403{
404 MONITORINFO info;
405 uint32_t mask;
406 int descr, rc = 0;
407
408 monitor_info_init (&info);
409
410 descr = monitor_resolve (&info, buffy);
411 if (descr != RESOLVERES_OK_NOTEXISTING)
412 {
413 if (!buffy && (descr == RESOLVERES_OK_EXISTING))
414 MonitorContextDescriptor = info.monitor->descr;
415 rc = descr == RESOLVERES_OK_EXISTING ? 0 : -1;
416 goto cleanup;
417 }
418
419 mask = info.isdir ? INOTIFY_MASK_DIR : INOTIFY_MASK_FILE;
420 if ((INotifyFd == -1 && monitor_init () == -1)
421 || (descr = inotify_add_watch (INotifyFd, info.path, mask)) == -1)
422 {
423 dprint (2, (debugfile, "monitor: inotify_add_watch failed for '%s', errno=%d %s\n", info.path, errno, strerror(errno)));
424 rc = -1;
425 goto cleanup;
426 }
427
428 dprint (3, (debugfile, "monitor: inotify_add_watch descriptor=%d for '%s'\n", descr, info.path));
429 if (!buffy)
430 MonitorContextDescriptor = descr;
431
432 monitor_create (&info, descr);
433
434cleanup:
435 monitor_info_free (&info);
436 return rc;
437}
438
439/* mutt_monitor_remove: remove file monitor from BUFFY, or - if NULL - from Context.
440 *
441 * return values:
442 * 0 monitor removed (not shared)
443 * 1 monitor not removed (shared)
444 * 2 no monitor
445 */
446int mutt_monitor_remove (BUFFY *buffy)
447{
448 MONITORINFO info, info2;
449 int rc = 0;
450
451 monitor_info_init (&info);
452 monitor_info_init (&info2);
453
454 if (!buffy)
455 {
456 MonitorContextDescriptor = -1;
457 MonitorContextChanged = 0;
458 }
459
460 if (monitor_resolve (&info, buffy) != RESOLVERES_OK_EXISTING)
461 {
462 rc = 2;
463 goto cleanup;
464 }
465
466 if (Context)
467 {
468 if (buffy)
469 {
470 if (monitor_resolve (&info2, NULL) == RESOLVERES_OK_EXISTING
471 && info.st_ino == info2.st_ino && info.st_dev == info2.st_dev)
472 {
473 rc = 1;
474 goto cleanup;
475 }
476 }
477 else
478 {
479 if (mutt_find_mailbox (Context->realpath))
480 {
481 rc = 1;
482 goto cleanup;
483 }
484 }
485 }
486
487 inotify_rm_watch(info.monitor->descr, INotifyFd);
488 dprint (3, (debugfile, "monitor: inotify_rm_watch for '%s' descriptor=%d\n", info.path, info.monitor->descr));
489
490 monitor_delete (info.monitor);
491 monitor_check_free ();
492
493cleanup:
494 monitor_info_free (&info);
495 monitor_info_free (&info2);
496 return rc;
497}