NeoMutt  2022-04-29-249-gaae397
Teaching an old dog new tricks
DOXYGEN
monitor.c
Go to the documentation of this file.
1
30#include "config.h"
31#include <errno.h>
32#include <limits.h>
33#include <poll.h>
34#include <stdbool.h>
35#include <stdint.h>
36#include <stdio.h>
37#include <string.h>
38#include <sys/inotify.h>
39#include <sys/stat.h>
40#include <unistd.h>
41#include "mutt/lib.h"
42#include "core/lib.h"
43#include "gui/lib.h"
44#include "monitor.h"
45#include "index/lib.h"
46#ifndef HAVE_INOTIFY_INIT1
47#include <fcntl.h>
48#endif
49
52
53static int INotifyFd = -1;
54static struct Monitor *Monitor = NULL;
55static size_t PollFdsCount = 0;
56static size_t PollFdsLen = 0;
57static struct pollfd *PollFds = NULL;
58
60
61#define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR)
62#define INOTIFY_MASK_FILE IN_CLOSE_WRITE
63
64#define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1)
65
70{
76};
77
81struct Monitor
82{
83 struct Monitor *next;
85 dev_t st_dev;
86 ino_t st_ino;
88 int desc;
89};
90
95{
97 bool is_dir;
98 const char *path;
99 dev_t st_dev;
100 ino_t st_ino;
103};
104
110static void mutt_poll_fd_add(int fd, short events)
111{
112 int i = 0;
113 for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
114 ; // do nothing
115
116 if (i == PollFdsCount)
117 {
119 {
120 PollFdsLen += 2;
121 mutt_mem_realloc(&PollFds, PollFdsLen * sizeof(struct pollfd));
122 }
123 PollFdsCount++;
124 PollFds[i].fd = fd;
125 PollFds[i].events = events;
126 }
127 else
128 PollFds[i].events |= events;
129}
130
137static int mutt_poll_fd_remove(int fd)
138{
139 int i = 0;
140 for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
141 ; // do nothing
142
143 if (i == PollFdsCount)
144 return -1;
145 int d = PollFdsCount - i - 1;
146 if (d != 0)
147 memmove(&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd));
148 PollFdsCount--;
149 return 0;
150}
151
157static int monitor_init(void)
158{
159 if (INotifyFd != -1)
160 return 0;
161
162#ifdef HAVE_INOTIFY_INIT1
163 INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
164 if (INotifyFd == -1)
165 {
166 mutt_debug(LL_DEBUG2, "inotify_init1 failed, errno=%d %s\n", errno, strerror(errno));
167 return -1;
168 }
169#else
170 INotifyFd = inotify_init();
171 if (INotifyFd == -1)
172 {
173 mutt_debug(LL_DEBUG2, "monitor: inotify_init failed, errno=%d %s\n", errno,
174 strerror(errno));
175 return -1;
176 }
177 fcntl(INotifyFd, F_SETFL, O_NONBLOCK);
178 fcntl(INotifyFd, F_SETFD, FD_CLOEXEC);
179#endif
180 mutt_poll_fd_add(0, POLLIN);
182
183 return 0;
184}
185
189static void monitor_check_free(void)
190{
191 if (!Monitor && (INotifyFd != -1))
192 {
194 close(INotifyFd);
195 INotifyFd = -1;
196 MonitorFilesChanged = false;
197 }
198}
199
206static struct Monitor *monitor_new(struct MonitorInfo *info, int descriptor)
207{
208 struct Monitor *monitor = mutt_mem_calloc(1, sizeof(struct Monitor));
209 monitor->type = info->type;
210 monitor->st_dev = info->st_dev;
211 monitor->st_ino = info->st_ino;
212 monitor->desc = descriptor;
213 monitor->next = Monitor;
214 if (info->type == MUTT_MH)
215 monitor->mh_backup_path = mutt_str_dup(info->path);
216
217 Monitor = monitor;
218
219 return monitor;
220}
221
226static void monitor_info_init(struct MonitorInfo *info)
227{
228 memset(info, 0, sizeof(*info));
229}
230
235static void monitor_info_free(struct MonitorInfo *info)
236{
238}
239
244static void monitor_delete(struct Monitor *monitor)
245{
246 if (!monitor)
247 return;
248
249 struct Monitor **ptr = &Monitor;
250
251 while (true)
252 {
253 if (!*ptr)
254 return;
255 if (*ptr == monitor)
256 break;
257 ptr = &(*ptr)->next;
258 }
259
260 FREE(&monitor->mh_backup_path);
261 monitor = monitor->next;
262 FREE(ptr);
263 *ptr = monitor;
264}
265
273{
274 int new_desc = -1;
275 struct Monitor *iter = Monitor;
276 struct stat st = { 0 };
277
278 while (iter && (iter->desc != desc))
279 iter = iter->next;
280
281 if (iter)
282 {
283 if ((iter->type == MUTT_MH) && (stat(iter->mh_backup_path, &st) == 0))
284 {
285 new_desc = inotify_add_watch(INotifyFd, iter->mh_backup_path, INOTIFY_MASK_FILE);
286 if (new_desc == -1)
287 {
288 mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
289 iter->mh_backup_path, errno, strerror(errno));
290 }
291 else
292 {
293 mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n",
294 desc, iter->mh_backup_path);
295 iter->st_dev = st.st_dev;
296 iter->st_ino = st.st_ino;
297 iter->desc = new_desc;
298 }
299 }
300 else
301 {
302 mutt_debug(LL_DEBUG3, "cleanup watch (implicitly removed) - descriptor=%d\n", desc);
303 }
304
305 if (MonitorContextDescriptor == desc)
306 MonitorContextDescriptor = new_desc;
307
308 if (new_desc == -1)
309 {
310 monitor_delete(iter);
312 }
313 }
314
315 return new_desc;
316}
317
330static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
331{
332 char *fmt = NULL;
333 struct stat st = { 0 };
334
335 struct Mailbox *m_cur = get_current_mailbox();
336 if (m)
337 {
338 info->type = m->type;
339 info->path = m->realpath;
340 }
341 else if (m_cur)
342 {
343 info->type = m_cur->type;
344 info->path = m_cur->realpath;
345 }
346 else
347 {
349 }
350
351 if (info->type == MUTT_UNKNOWN)
352 {
354 }
355 else if (info->type == MUTT_MAILDIR)
356 {
357 info->is_dir = true;
358 fmt = "%s/new";
359 }
360 else
361 {
362 info->is_dir = false;
363 if (info->type == MUTT_MH)
364 fmt = "%s/.mh_sequences";
365 }
366 if (fmt)
367 {
368 mutt_buffer_printf(&info->path_buf, fmt, info->path);
369 info->path = mutt_buffer_string(&info->path_buf);
370 }
371 if (stat(info->path, &st) != 0)
373
374 struct Monitor *iter = Monitor;
375 while (iter && ((iter->st_ino != st.st_ino) || (iter->st_dev != st.st_dev)))
376 iter = iter->next;
377
378 info->st_dev = st.st_dev;
379 info->st_ino = st.st_ino;
380 info->monitor = iter;
381
383}
384
400{
401 int rc = 0;
402 char buf[EVENT_BUFLEN] __attribute__((aligned(__alignof__(struct inotify_event))));
403
404 MonitorFilesChanged = false;
405
406 if (INotifyFd != -1)
407 {
408 int fds = poll(PollFds, PollFdsCount, MuttGetchTimeout);
409
410 if (fds == -1)
411 {
412 rc = -1;
413 if (errno != EINTR)
414 {
415 mutt_debug(LL_DEBUG2, "poll() failed, errno=%d %s\n", errno, strerror(errno));
416 }
417 }
418 else
419 {
420 bool input_ready = false;
421 for (int i = 0; fds && (i < PollFdsCount); i++)
422 {
423 if (PollFds[i].revents)
424 {
425 fds--;
426 if (PollFds[i].fd == 0)
427 {
428 input_ready = true;
429 }
430 else if (PollFds[i].fd == INotifyFd)
431 {
432 MonitorFilesChanged = true;
433 mutt_debug(LL_DEBUG3, "file change(s) detected\n");
434 char *ptr = buf;
435 const struct inotify_event *event = NULL;
436
437 while (true)
438 {
439 int len = read(INotifyFd, buf, sizeof(buf));
440 if (len == -1)
441 {
442 if (errno != EAGAIN)
443 {
444 mutt_debug(LL_DEBUG2, "read inotify events failed, errno=%d %s\n",
445 errno, strerror(errno));
446 }
447 break;
448 }
449
450 while (ptr < (buf + len))
451 {
452 event = (const struct inotify_event *) ptr;
453 mutt_debug(LL_DEBUG3, "+ detail: descriptor=%d mask=0x%x\n",
454 event->wd, event->mask);
455 if (event->mask & IN_IGNORED)
456 monitor_handle_ignore(event->wd);
457 else if (event->wd == MonitorContextDescriptor)
459 ptr += sizeof(struct inotify_event) + event->len;
460 }
461 }
462 }
463 }
464 }
465 if (!input_ready)
466 rc = MonitorFilesChanged ? -2 : -3;
467 }
468 }
469
470 return rc;
471}
472
482{
483 struct MonitorInfo info;
484 monitor_info_init(&info);
485
486 int rc = 0;
487 enum ResolveResult desc = monitor_resolve(&info, m);
488 if (desc != RESOLVE_RES_OK_NOTEXISTING)
489 {
490 if (!m && (desc == RESOLVE_RES_OK_EXISTING))
492 rc = (desc == RESOLVE_RES_OK_EXISTING) ? 0 : -1;
493 goto cleanup;
494 }
495
496 uint32_t mask = info.is_dir ? INOTIFY_MASK_DIR : INOTIFY_MASK_FILE;
497 if (((INotifyFd == -1) && (monitor_init() == -1)) ||
498 ((desc = inotify_add_watch(INotifyFd, info.path, mask)) == -1))
499 {
500 mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
501 info.path, errno, strerror(errno));
502 rc = -1;
503 goto cleanup;
504 }
505
506 mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n", desc, info.path);
507 if (!m)
509
510 monitor_new(&info, desc);
511
512cleanup:
513 monitor_info_free(&info);
514 return rc;
515}
516
527{
528 struct MonitorInfo info, info2;
529 int rc = 0;
530
531 monitor_info_init(&info);
532 monitor_info_init(&info2);
533
534 if (!m)
535 {
537 MonitorContextChanged = false;
538 }
539
541 {
542 rc = 2;
543 goto cleanup;
544 }
545
546 struct Mailbox *m_cur = get_current_mailbox();
547 if (m_cur)
548 {
549 if (m)
550 {
551 if ((monitor_resolve(&info2, NULL) == RESOLVE_RES_OK_EXISTING) &&
552 (info.st_ino == info2.st_ino) && (info.st_dev == info2.st_dev))
553 {
554 rc = 1;
555 goto cleanup;
556 }
557 }
558 else
559 {
560 if (mailbox_find(m_cur->realpath))
561 {
562 rc = 1;
563 goto cleanup;
564 }
565 }
566 }
567
568 inotify_rm_watch(info.monitor->desc, INotifyFd);
569 mutt_debug(LL_DEBUG3, "inotify_rm_watch for '%s' descriptor=%d\n", info.path,
570 info.monitor->desc);
571
574
575cleanup:
576 monitor_info_free(&info);
577 monitor_info_free(&info2);
578 return rc;
579}
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:309
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:168
static const char * mutt_buffer_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:77
Convenience wrapper for the core headers.
int MuttGetchTimeout
Timeout in ms for mutt_getch()
Definition: curs_lib.c:124
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
Convenience wrapper for the gui headers.
GUI manage the main index (list of emails)
struct Mailbox * get_current_mailbox(void)
Get the current Mailbox.
Definition: index.c:618
@ LL_DEBUG3
Log at debug level 3.
Definition: logging.h:42
@ LL_DEBUG2
Log at debug level 2.
Definition: logging.h:41
struct Mailbox * mailbox_find(const char *path)
Find the mailbox with a given path.
Definition: mailbox.c:139
MailboxType
Supported mailbox formats.
Definition: mailbox.h:41
@ MUTT_MH
'MH' Mailbox type
Definition: mailbox.h:47
@ MUTT_UNKNOWN
Mailbox wasn't recognised.
Definition: mailbox.h:44
@ MUTT_MAILDIR
'Maildir' Mailbox type
Definition: mailbox.h:48
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
#define FREE(x)
Definition: memory.h:43
#define INOTIFY_MASK_DIR
Definition: monitor.c:61
int mutt_monitor_add(struct Mailbox *m)
Add a watch for a mailbox.
Definition: monitor.c:481
static void monitor_delete(struct Monitor *monitor)
Free a file monitor.
Definition: monitor.c:244
static int MonitorContextDescriptor
Definition: monitor.c:59
static int mutt_poll_fd_remove(int fd)
Remove a file from the watch list.
Definition: monitor.c:137
static int INotifyFd
Definition: monitor.c:53
static int monitor_handle_ignore(int desc)
Listen for when a backup file is closed.
Definition: monitor.c:272
static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
Get the monitor for a mailbox.
Definition: monitor.c:330
static struct Monitor * monitor_new(struct MonitorInfo *info, int descriptor)
Create a new file monitor.
Definition: monitor.c:206
int mutt_monitor_poll(void)
Check for filesystem changes.
Definition: monitor.c:399
static size_t PollFdsCount
Definition: monitor.c:55
static void mutt_poll_fd_add(int fd, short events)
Add a file to the watch list.
Definition: monitor.c:110
ResolveResult
Results for the Monitor functions.
Definition: monitor.c:70
@ RESOLVE_RES_FAIL_NOTYPE
Can't identify Mailbox type.
Definition: monitor.c:72
@ RESOLVE_RES_FAIL_STAT
Can't stat() the Mailbox file.
Definition: monitor.c:73
@ RESOLVE_RES_OK_NOTEXISTING
File exists, no monitor is attached.
Definition: monitor.c:74
@ RESOLVE_RES_FAIL_NOMAILBOX
No Mailbox to work on.
Definition: monitor.c:71
@ RESOLVE_RES_OK_EXISTING
File exists, monitor is already attached.
Definition: monitor.c:75
bool MonitorFilesChanged
true after a monitored file has changed
Definition: monitor.c:50
static void monitor_check_free(void)
Close down file monitoring.
Definition: monitor.c:189
static int monitor_init(void)
Set up file monitoring.
Definition: monitor.c:157
static struct Monitor * Monitor
Definition: monitor.c:54
bool MonitorContextChanged
true after the current mailbox has changed
Definition: monitor.c:51
static void monitor_info_init(struct MonitorInfo *info)
Set up a file monitor.
Definition: monitor.c:226
#define INOTIFY_MASK_FILE
Definition: monitor.c:62
#define EVENT_BUFLEN
Definition: monitor.c:64
static void monitor_info_free(struct MonitorInfo *info)
Shutdown a file monitor.
Definition: monitor.c:235
int mutt_monitor_remove(struct Mailbox *m)
Remove a watch for a mailbox.
Definition: monitor.c:526
static size_t PollFdsLen
Definition: monitor.c:56
static struct pollfd * PollFds
Definition: monitor.c:57
Monitor files for changes.
Convenience wrapper for the library headers.
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
String manipulation buffer.
Definition: buffer.h:34
A mailbox.
Definition: mailbox.h:79
char * realpath
Used for duplicate detection, context comparison, and the sidebar.
Definition: mailbox.h:81
enum MailboxType type
Mailbox type.
Definition: mailbox.h:102
Information about a monitored file.
Definition: monitor.c:95
dev_t st_dev
Definition: monitor.c:99
enum MailboxType type
Definition: monitor.c:96
struct Monitor * monitor
Definition: monitor.c:101
bool is_dir
Definition: monitor.c:97
ino_t st_ino
Definition: monitor.c:100
const char * path
Definition: monitor.c:98
struct Buffer path_buf
access via path only (maybe not initialized)
Definition: monitor.c:102
A watch on a file.
Definition: monitor.c:82
struct Monitor * next
Linked list.
Definition: monitor.c:83
ino_t st_ino
Definition: monitor.c:86
dev_t st_dev
Definition: monitor.c:85
int desc
Definition: monitor.c:88
enum MailboxType type
Definition: monitor.c:87
char * mh_backup_path
Definition: monitor.c:84