]> granicus.if.org Git - mutt/blob - monitor.c
Convert pgp_app_handler to use buffer pool.
[mutt] / monitor.c
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
41 typedef 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 }
50 MONITOR;
51
52 static int INotifyFd = -1;
53 static MONITOR *Monitor = NULL;
54 static size_t PollFdsCount = 0;
55 static size_t PollFdsLen = 0;
56 static struct pollfd *PollFds;
57
58 static int MonitorContextDescriptor = -1;
59
60 typedef 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 }
70 MONITORINFO;
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
75 static 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
95 static 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
108 static 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
135 static 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
146 static 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
162 static void monitor_info_init (MONITORINFO *info)
163 {
164   memset (info, 0, sizeof (MONITORINFO));
165 }
166
167 static void monitor_info_free (MONITORINFO *info)
168 {
169   mutt_buffer_free (&info->_pathbuf);
170 }
171
172 static 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
194 static 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  */
249 int 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  */
338 static 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  */
402 int 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
434 cleanup:
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  */
446 int 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
493 cleanup:
494   monitor_info_free (&info);
495   monitor_info_free (&info2);
496   return rc;
497 }