NeoMutt
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
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 <string.h>
37#include <sys/inotify.h>
38#include <sys/stat.h>
39#include <unistd.h>
40#include "mutt/lib.h"
41#include "core/lib.h"
42#include "monitor.h"
43#include "index/lib.h"
44#ifndef HAVE_INOTIFY_INIT1
45#include <fcntl.h>
46#endif
47
52
54static int INotifyFd = -1;
56static struct Monitor *Monitor = NULL;
58static size_t PollFdsCount = 0;
60static size_t PollFdsLen = 0;
62static struct pollfd *PollFds = NULL;
65
66#define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR)
67#define INOTIFY_MASK_FILE IN_CLOSE_WRITE
68
69#define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1)
70
75{
81};
82
86struct Monitor
87{
88 struct Monitor *next;
90 dev_t st_dev;
91 ino_t st_ino;
93 int desc;
94};
95
100{
102 bool is_dir;
103 const char *path;
104 dev_t st_dev;
105 ino_t st_ino;
108};
109
115static void mutt_poll_fd_add(int fd, short events)
116{
117 int i = 0;
118 for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
119 ; // do nothing
120
121 if (i == PollFdsCount)
122 {
124 {
125 PollFdsLen += 2;
126 mutt_mem_realloc(&PollFds, PollFdsLen * sizeof(struct pollfd));
127 }
128 PollFdsCount++;
129 PollFds[i].fd = fd;
130 PollFds[i].events = events;
131 }
132 else
133 {
134 PollFds[i].events |= events;
135 }
136}
137
144static int mutt_poll_fd_remove(int fd)
145{
146 int i = 0;
147 for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
148 ; // do nothing
149
150 if (i == PollFdsCount)
151 return -1;
152 int d = PollFdsCount - i - 1;
153 if (d != 0)
154 memmove(&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd));
155 PollFdsCount--;
156 return 0;
157}
158
164static int monitor_init(void)
165{
166 if (INotifyFd != -1)
167 return 0;
168
169#ifdef HAVE_INOTIFY_INIT1
170 INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
171 if (INotifyFd == -1)
172 {
173 mutt_debug(LL_DEBUG2, "inotify_init1 failed, errno=%d %s\n", errno, strerror(errno));
174 return -1;
175 }
176#else
177 INotifyFd = inotify_init();
178 if (INotifyFd == -1)
179 {
180 mutt_debug(LL_DEBUG2, "monitor: inotify_init failed, errno=%d %s\n", errno,
181 strerror(errno));
182 return -1;
183 }
184 fcntl(INotifyFd, F_SETFL, O_NONBLOCK);
185 fcntl(INotifyFd, F_SETFD, FD_CLOEXEC);
186#endif
187 mutt_poll_fd_add(0, POLLIN);
189
190 return 0;
191}
192
196static void monitor_check_cleanup(void)
197{
198 if (!Monitor && (INotifyFd != -1))
199 {
201 close(INotifyFd);
202 INotifyFd = -1;
203 MonitorFilesChanged = false;
204 }
205}
206
213static struct Monitor *monitor_new(struct MonitorInfo *info, int descriptor)
214{
215 struct Monitor *monitor = mutt_mem_calloc(1, sizeof(struct Monitor));
216 monitor->type = info->type;
217 monitor->st_dev = info->st_dev;
218 monitor->st_ino = info->st_ino;
219 monitor->desc = descriptor;
220 monitor->next = Monitor;
221 if (info->type == MUTT_MH)
222 monitor->mh_backup_path = mutt_str_dup(info->path);
223
224 Monitor = monitor;
225
226 return monitor;
227}
228
233static void monitor_info_free(struct MonitorInfo *info)
234{
235 buf_dealloc(&info->path_buf);
236}
237
242static void monitor_delete(struct Monitor *monitor)
243{
244 if (!monitor)
245 return;
246
247 struct Monitor **ptr = &Monitor;
248
249 while (true)
250 {
251 if (!*ptr)
252 return;
253 if (*ptr == monitor)
254 break;
255 ptr = &(*ptr)->next;
256 }
257
258 FREE(&monitor->mh_backup_path);
259 monitor = monitor->next;
260 FREE(ptr);
261 *ptr = monitor;
262}
263
271{
272 int new_desc = -1;
273 struct Monitor *iter = Monitor;
274 struct stat st = { 0 };
275
276 while (iter && (iter->desc != desc))
277 iter = iter->next;
278
279 if (iter)
280 {
281 if ((iter->type == MUTT_MH) && (stat(iter->mh_backup_path, &st) == 0))
282 {
283 new_desc = inotify_add_watch(INotifyFd, iter->mh_backup_path, INOTIFY_MASK_FILE);
284 if (new_desc == -1)
285 {
286 mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
287 iter->mh_backup_path, errno, strerror(errno));
288 }
289 else
290 {
291 mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n",
292 desc, iter->mh_backup_path);
293 iter->st_dev = st.st_dev;
294 iter->st_ino = st.st_ino;
295 iter->desc = new_desc;
296 }
297 }
298 else
299 {
300 mutt_debug(LL_DEBUG3, "cleanup watch (implicitly removed) - descriptor=%d\n", desc);
301 }
302
303 if (MonitorContextDescriptor == desc)
304 MonitorContextDescriptor = new_desc;
305
306 if (new_desc == -1)
307 {
308 monitor_delete(iter);
310 }
311 }
312
313 return new_desc;
314}
315
328static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
329{
330 char *fmt = NULL;
331 struct stat st = { 0 };
332
333 struct Mailbox *m_cur = get_current_mailbox();
334 if (m)
335 {
336 info->type = m->type;
337 info->path = m->realpath;
338 }
339 else if (m_cur)
340 {
341 info->type = m_cur->type;
342 info->path = m_cur->realpath;
343 }
344 else
345 {
347 }
348
349 if (info->type == MUTT_UNKNOWN)
350 {
352 }
353 else if (info->type == MUTT_MAILDIR)
354 {
355 info->is_dir = true;
356 fmt = "%s/new";
357 }
358 else
359 {
360 info->is_dir = false;
361 if (info->type == MUTT_MH)
362 fmt = "%s/.mh_sequences";
363 }
364 if (fmt)
365 {
366 buf_printf(&info->path_buf, fmt, info->path);
367 info->path = buf_string(&info->path_buf);
368 }
369 if (stat(info->path, &st) != 0)
371
372 struct Monitor *iter = Monitor;
373 while (iter && ((iter->st_ino != st.st_ino) || (iter->st_dev != st.st_dev)))
374 iter = iter->next;
375
376 info->st_dev = st.st_dev;
377 info->st_ino = st.st_ino;
378 info->monitor = iter;
379
381}
382
398{
399 int rc = 0;
400 char buf[EVENT_BUFLEN]
401 __attribute__((aligned(__alignof__(struct inotify_event)))) = { 0 };
402
403 MonitorFilesChanged = false;
404
405 if (INotifyFd != -1)
406 {
407 int fds = poll(PollFds, PollFdsCount, 1000); // 1 Second
408
409 if (fds == -1)
410 {
411 rc = -1;
412 if (errno != EINTR)
413 {
414 mutt_debug(LL_DEBUG2, "poll() failed, errno=%d %s\n", errno, strerror(errno));
415 }
416 }
417 else
418 {
419 bool input_ready = false;
420 for (int i = 0; fds && (i < PollFdsCount); i++)
421 {
422 if (PollFds[i].revents)
423 {
424 fds--;
425 if (PollFds[i].fd == 0)
426 {
427 input_ready = true;
428 }
429 else if (PollFds[i].fd == INotifyFd)
430 {
431 MonitorFilesChanged = true;
432 mutt_debug(LL_DEBUG3, "file change(s) detected\n");
433 char *ptr = buf;
434 const struct inotify_event *event = NULL;
435
436 while (true)
437 {
438 int len = read(INotifyFd, buf, sizeof(buf));
439 if (len == -1)
440 {
441 if (errno != EAGAIN)
442 {
443 mutt_debug(LL_DEBUG2, "read inotify events failed, errno=%d %s\n",
444 errno, strerror(errno));
445 }
446 break;
447 }
448
449 while (ptr < (buf + len))
450 {
451 event = (const struct inotify_event *) ptr;
452 mutt_debug(LL_DEBUG3, "+ detail: descriptor=%d mask=0x%x\n",
453 event->wd, event->mask);
454 if (event->mask & IN_IGNORED)
455 monitor_handle_ignore(event->wd);
456 else if (event->wd == MonitorContextDescriptor)
458 ptr += sizeof(struct inotify_event) + event->len;
459 }
460 }
461 }
462 }
463 }
464 if (!input_ready)
465 rc = MonitorFilesChanged ? -2 : -3;
466 }
467 }
468
469 return rc;
470}
471
481{
482 struct MonitorInfo info = { 0 };
483
484 int rc = 0;
485 enum ResolveResult desc = monitor_resolve(&info, m);
486 if (desc != RESOLVE_RES_OK_NOTEXISTING)
487 {
488 if (!m && (desc == RESOLVE_RES_OK_EXISTING))
490 rc = (desc == RESOLVE_RES_OK_EXISTING) ? 0 : -1;
491 goto cleanup;
492 }
493
494 uint32_t mask = info.is_dir ? INOTIFY_MASK_DIR : INOTIFY_MASK_FILE;
495 if (((INotifyFd == -1) && (monitor_init() == -1)) ||
496 ((desc = inotify_add_watch(INotifyFd, info.path, mask)) == -1))
497 {
498 mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
499 info.path, errno, strerror(errno));
500 rc = -1;
501 goto cleanup;
502 }
503
504 mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n", desc, info.path);
505 if (!m)
507
508 monitor_new(&info, desc);
509
510cleanup:
511 monitor_info_free(&info);
512 return rc;
513}
514
525{
526 struct MonitorInfo info = { 0 };
527 struct MonitorInfo info2 = { 0 };
528 int rc = 0;
529
530 if (!m)
531 {
533 MonitorContextChanged = false;
534 }
535
537 {
538 rc = 2;
539 goto cleanup;
540 }
541
542 struct Mailbox *m_cur = get_current_mailbox();
543 if (m_cur)
544 {
545 if (m)
546 {
547 if ((monitor_resolve(&info2, NULL) == RESOLVE_RES_OK_EXISTING) &&
548 (info.st_ino == info2.st_ino) && (info.st_dev == info2.st_dev))
549 {
550 rc = 1;
551 goto cleanup;
552 }
553 }
554 else
555 {
556 if (mailbox_find(m_cur->realpath))
557 {
558 rc = 1;
559 goto cleanup;
560 }
561 }
562 }
563
564 inotify_rm_watch(info.monitor->desc, INotifyFd);
565 mutt_debug(LL_DEBUG3, "inotify_rm_watch for '%s' descriptor=%d\n", info.path,
566 info.monitor->desc);
567
570
571cleanup:
572 monitor_info_free(&info);
573 monitor_info_free(&info2);
574 return rc;
575}
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:173
void buf_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:389
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:93
Convenience wrapper for the core headers.
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
GUI manage the main index (list of emails)
struct Mailbox * get_current_mailbox(void)
Get the current Mailbox.
Definition: index.c:662
@ LL_DEBUG3
Log at debug level 3.
Definition: logging2.h:45
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
struct Mailbox * mailbox_find(const char *path)
Find the mailbox with a given path.
Definition: mailbox.c:143
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:45
#define INOTIFY_MASK_DIR
Definition: monitor.c:66
int mutt_monitor_add(struct Mailbox *m)
Add a watch for a mailbox.
Definition: monitor.c:480
static void monitor_delete(struct Monitor *monitor)
Free a file monitor.
Definition: monitor.c:242
static int MonitorContextDescriptor
Monitor file descriptor of the current mailbox.
Definition: monitor.c:64
static int mutt_poll_fd_remove(int fd)
Remove a file from the watch list.
Definition: monitor.c:144
static int INotifyFd
Inotify file descriptor.
Definition: monitor.c:54
static int monitor_handle_ignore(int desc)
Listen for when a backup file is closed.
Definition: monitor.c:270
static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
Get the monitor for a mailbox.
Definition: monitor.c:328
static struct Monitor * monitor_new(struct MonitorInfo *info, int descriptor)
Create a new file monitor.
Definition: monitor.c:213
int mutt_monitor_poll(void)
Check for filesystem changes.
Definition: monitor.c:397
static size_t PollFdsCount
Number of used entries in the PollFds array.
Definition: monitor.c:58
static void mutt_poll_fd_add(int fd, short events)
Add a file to the watch list.
Definition: monitor.c:115
ResolveResult
Results for the Monitor functions.
Definition: monitor.c:75
@ RESOLVE_RES_FAIL_NOTYPE
Can't identify Mailbox type.
Definition: monitor.c:77
@ RESOLVE_RES_FAIL_STAT
Can't stat() the Mailbox file.
Definition: monitor.c:78
@ RESOLVE_RES_OK_NOTEXISTING
File exists, no monitor is attached.
Definition: monitor.c:79
@ RESOLVE_RES_FAIL_NOMAILBOX
No Mailbox to work on.
Definition: monitor.c:76
@ RESOLVE_RES_OK_EXISTING
File exists, monitor is already attached.
Definition: monitor.c:80
bool MonitorFilesChanged
Set to true when a monitored file has changed.
Definition: monitor.c:49
static int monitor_init(void)
Set up file monitoring.
Definition: monitor.c:164
bool MonitorContextChanged
Set to true when the current mailbox has changed.
Definition: monitor.c:51
static void monitor_check_cleanup(void)
Close down file monitoring.
Definition: monitor.c:196
#define INOTIFY_MASK_FILE
Definition: monitor.c:67
#define EVENT_BUFLEN
Definition: monitor.c:69
static void monitor_info_free(struct MonitorInfo *info)
Shutdown a file monitor.
Definition: monitor.c:233
int mutt_monitor_remove(struct Mailbox *m)
Remove a watch for a mailbox.
Definition: monitor.c:524
static size_t PollFdsLen
Size of PollFds array.
Definition: monitor.c:60
static struct pollfd * PollFds
Array of monitored file descriptors.
Definition: monitor.c:62
Monitor files for changes.
Convenience wrapper for the library headers.
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
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:100
dev_t st_dev
Definition: monitor.c:104
enum MailboxType type
Definition: monitor.c:101
struct Monitor * monitor
Definition: monitor.c:106
bool is_dir
Definition: monitor.c:102
ino_t st_ino
Definition: monitor.c:105
const char * path
Definition: monitor.c:103
struct Buffer path_buf
access via path only (maybe not initialized)
Definition: monitor.c:107
A watch on a file.
Definition: monitor.c:87
struct Monitor * next
Linked list.
Definition: monitor.c:88
ino_t st_ino
Definition: monitor.c:91
dev_t st_dev
Definition: monitor.c:90
int desc
Definition: monitor.c:93
enum MailboxType type
Definition: monitor.c:92
char * mh_backup_path
Definition: monitor.c:89