1 /* * Copyright (C) 2018 Gero Treuner <gero@70t.de>
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.
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.
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.
22 #if HAVE_SYS_INOTIFY_H
23 # include <sys/types.h>
24 # include <sys/inotify.h>
28 #ifndef HAVE_INOTIFY_INIT1
36 #include "mutt_curses.h"
41 typedef struct monitor_t
43 struct monitor_t *next;
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;
58 static int MonitorContextDescriptor = -1;
60 typedef struct monitorinfo_t
68 BUFFER *_pathbuf; /* access via path only (maybe not initialized) */
72 #define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR)
73 #define INOTIFY_MASK_FILE IN_CLOSE_WRITE
75 static void mutt_poll_fd_add(int fd, short events)
78 for (i = 0; i < PollFdsCount && PollFds[i].fd != fd; ++i);
80 if (i == PollFdsCount)
82 if (PollFdsCount == PollFdsLen)
85 safe_realloc (&PollFds, PollFdsLen * sizeof(struct pollfd));
89 PollFds[i].events = events;
92 PollFds[i].events |= events;
95 static int mutt_poll_fd_remove(int fd)
98 for (i = 0; i < PollFdsCount && PollFds[i].fd != fd; ++i);
99 if (i == PollFdsCount)
101 d = PollFdsCount - i - 1;
103 memmove (&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd));
108 static int monitor_init ()
112 #if HAVE_INOTIFY_INIT1
113 INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
116 dprint (2, (debugfile, "monitor: inotify_init1 failed, errno=%d %s\n", errno, strerror(errno)));
120 INotifyFd = inotify_init();
123 dprint (2, (debugfile, "monitor: inotify_init failed, errno=%d %s\n", errno, strerror(errno)));
126 fcntl(INotifyFd, F_SETFL, O_NONBLOCK);
127 fcntl(INotifyFd, F_SETFD, FD_CLOEXEC);
129 mutt_poll_fd_add(0, POLLIN);
130 mutt_poll_fd_add(INotifyFd, POLLIN);
135 static void monitor_check_free ()
137 if (!Monitor && INotifyFd != -1)
139 mutt_poll_fd_remove(INotifyFd);
142 MonitorFilesChanged = 0;
146 static MONITOR *monitor_create (MONITORINFO *info, int descriptor)
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);
162 static void monitor_info_init (MONITORINFO *info)
164 memset (info, 0, sizeof (MONITORINFO));
167 static void monitor_info_free (MONITORINFO *info)
169 mutt_buffer_free (&info->_pathbuf);
172 static void monitor_delete (MONITOR *monitor)
174 MONITOR **ptr = &Monitor;
188 FREE (&monitor->mh_backup_path); /* __FREE_CHECKED__ */
189 monitor = monitor->next;
190 FREE (ptr); /* __FREE_CHECKED__ */
194 static int monitor_handle_ignore (int descr)
197 MONITOR *iter = Monitor;
200 while (iter && iter->descr != descr)
205 if (iter->magic == MUTT_MH && stat (iter->mh_backup_path, &sb) == 0)
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)));
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;
219 dprint (3, (debugfile, "monitor: cleanup watch (implicitly removed) - descriptor=%d\n", descr));
222 if (MonitorContextDescriptor == descr)
223 MonitorContextDescriptor = new_descr;
227 monitor_delete (iter);
228 monitor_check_free ();
235 #define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1)
237 /* mutt_monitor_poll: Waits for I/O ready file descriptors or signals.
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.
246 * Only STDIN and INotify file handles currently expected/supported.
247 * More would ask for common infrastructur (sockets?).
249 int mutt_monitor_poll (void)
251 int rc = 0, fds, i, inputReady;
252 char buf[EVENT_BUFLEN]
253 __attribute__ ((aligned(__alignof__(struct inotify_event))));
255 MonitorFilesChanged = 0;
259 fds = poll (PollFds, PollFdsLen, MuttGetchTimeout);
266 dprint (2, (debugfile, "monitor: poll() failed, errno=%d %s\n", errno, strerror(errno)));
272 for (i = 0; fds && i < PollFdsCount; ++i)
274 if (PollFds[i].revents)
277 if (PollFds[i].fd == 0)
281 else if (PollFds[i].fd == INotifyFd)
283 MonitorFilesChanged = 1;
284 dprint (3, (debugfile, "monitor: file change(s) detected\n"));
287 const struct inotify_event *event;
291 len = read (INotifyFd, buf, sizeof(buf));
295 dprint (2, (debugfile, "monitor: read inotify events failed, errno=%d %s\n",
296 errno, strerror(errno)));
300 while (ptr < buf + len)
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;
316 rc = MonitorFilesChanged ? -2 : -3;
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
329 /* monitor_resolve: resolve monitor entry match by BUFFY, or - if NULL - by Context.
332 * >=0 mailbox is valid and locally accessible:
333 * 0: no monitor / 1: preexisting monitor
334 * -3 no mailbox (MONITORINFO: no fields set)
336 * -1 stat() failed (see errno; MONITORINFO fields: magic, isdir, path)
338 static int monitor_resolve (MONITORINFO *info, BUFFY *buffy)
346 info->magic = buffy->magic;
347 info->path = buffy->realpath;
351 info->magic = Context->magic;
352 info->path = Context->realpath;
356 return RESOLVERES_FAIL_NOMAILBOX;
361 return RESOLVERES_FAIL_NOMAGIC;
363 else if (info->magic == MUTT_MAILDIR)
371 if (info->magic == MUTT_MH)
372 fmt = "%s/.mh_sequences";
378 info->_pathbuf = mutt_buffer_new ();
379 mutt_buffer_printf (info->_pathbuf, fmt, info->path);
380 info->path = mutt_b2s (info->_pathbuf);
382 if (stat (info->path, &sb) != 0)
383 return RESOLVERES_FAIL_STAT;
386 while (iter && (iter->st_ino != sb.st_ino || iter->st_dev != sb.st_dev))
389 info->st_dev = sb.st_dev;
390 info->st_ino = sb.st_ino;
391 info->monitor = iter;
393 return iter ? RESOLVERES_OK_EXISTING : RESOLVERES_OK_NOTEXISTING;
396 /* mutt_monitor_add: add file monitor from BUFFY, or - if NULL - from Context.
399 * 0 success: new or already existing monitor
400 * -1 failed: no mailbox, inaccessible file, create monitor/watcher failed
402 int mutt_monitor_add (BUFFY *buffy)
408 monitor_info_init (&info);
410 descr = monitor_resolve (&info, buffy);
411 if (descr != RESOLVERES_OK_NOTEXISTING)
413 if (!buffy && (descr == RESOLVERES_OK_EXISTING))
414 MonitorContextDescriptor = info.monitor->descr;
415 rc = descr == RESOLVERES_OK_EXISTING ? 0 : -1;
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)
423 dprint (2, (debugfile, "monitor: inotify_add_watch failed for '%s', errno=%d %s\n", info.path, errno, strerror(errno)));
428 dprint (3, (debugfile, "monitor: inotify_add_watch descriptor=%d for '%s'\n", descr, info.path));
430 MonitorContextDescriptor = descr;
432 monitor_create (&info, descr);
435 monitor_info_free (&info);
439 /* mutt_monitor_remove: remove file monitor from BUFFY, or - if NULL - from Context.
442 * 0 monitor removed (not shared)
443 * 1 monitor not removed (shared)
446 int mutt_monitor_remove (BUFFY *buffy)
448 MONITORINFO info, info2;
451 monitor_info_init (&info);
452 monitor_info_init (&info2);
456 MonitorContextDescriptor = -1;
457 MonitorContextChanged = 0;
460 if (monitor_resolve (&info, buffy) != RESOLVERES_OK_EXISTING)
470 if (monitor_resolve (&info2, NULL) == RESOLVERES_OK_EXISTING
471 && info.st_ino == info2.st_ino && info.st_dev == info2.st_dev)
479 if (mutt_find_mailbox (Context->realpath))
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));
490 monitor_delete (info.monitor);
491 monitor_check_free ();
494 monitor_info_free (&info);
495 monitor_info_free (&info2);