NeoMutt  2024-10-02-37-gfa9146
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
monitor.c
Go to the documentation of this file.
1
33#include "config.h"
34#include <errno.h>
35#include <limits.h>
36#include <poll.h>
37#include <stdbool.h>
38#include <stdint.h>
39#include <string.h>
40#include <sys/inotify.h>
41#include <sys/stat.h>
42#include <sys/types.h>
43#include <unistd.h>
44#include "mutt/lib.h"
45#include "core/lib.h"
46#include "monitor.h"
47#include "index/lib.h"
48#ifndef HAVE_INOTIFY_INIT1
49#include <fcntl.h>
50#endif
51
56
58static int INotifyFd = -1;
60static struct Monitor *Monitor = NULL;
62static size_t PollFdsCount = 0;
64static size_t PollFdsLen = 0;
66static struct pollfd *PollFds = NULL;
69
70#define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR)
71#define INOTIFY_MASK_FILE IN_CLOSE_WRITE
72
73#define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1)
74
79{
85};
86
90struct Monitor
91{
92 struct Monitor *next;
94 dev_t st_dev;
95 ino_t st_ino;
97 int desc;
98};
99
104{
106 bool is_dir;
107 const char *path;
108 dev_t st_dev;
109 ino_t st_ino;
112};
113
119static void mutt_poll_fd_add(int fd, short events)
120{
121 int i = 0;
122 for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
123 ; // do nothing
124
125 if (i == PollFdsCount)
126 {
128 {
129 PollFdsLen += 2;
130 mutt_mem_realloc(&PollFds, PollFdsLen * sizeof(struct pollfd));
131 }
132 PollFdsCount++;
133 PollFds[i].fd = fd;
134 PollFds[i].events = events;
135 }
136 else
137 {
138 PollFds[i].events |= events;
139 }
140}
141
148static int mutt_poll_fd_remove(int fd)
149{
150 int i = 0;
151 for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
152 ; // do nothing
153
154 if (i == PollFdsCount)
155 return -1;
156 int d = PollFdsCount - i - 1;
157 if (d != 0)
158 memmove(&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd));
159 PollFdsCount--;
160 return 0;
161}
162
168static int monitor_init(void)
169{
170 if (INotifyFd != -1)
171 return 0;
172
173#ifdef HAVE_INOTIFY_INIT1
174 INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
175 if (INotifyFd == -1)
176 {
177 mutt_debug(LL_DEBUG2, "inotify_init1 failed, errno=%d %s\n", errno, strerror(errno));
178 return -1;
179 }
180#else
181 INotifyFd = inotify_init();
182 if (INotifyFd == -1)
183 {
184 mutt_debug(LL_DEBUG2, "monitor: inotify_init failed, errno=%d %s\n", errno,
185 strerror(errno));
186 return -1;
187 }
188 fcntl(INotifyFd, F_SETFL, O_NONBLOCK);
189 fcntl(INotifyFd, F_SETFD, FD_CLOEXEC);
190#endif
191 mutt_poll_fd_add(0, POLLIN);
193
194 return 0;
195}
196
200static void monitor_check_cleanup(void)
201{
202 if (!Monitor && (INotifyFd != -1))
203 {
205 close(INotifyFd);
206 INotifyFd = -1;
207 MonitorFilesChanged = false;
208 }
209}
210
217static struct Monitor *monitor_new(struct MonitorInfo *info, int descriptor)
218{
219 struct Monitor *monitor = mutt_mem_calloc(1, sizeof(struct Monitor));
220 monitor->type = info->type;
221 monitor->st_dev = info->st_dev;
222 monitor->st_ino = info->st_ino;
223 monitor->desc = descriptor;
224 monitor->next = Monitor;
225 if (info->type == MUTT_MH)
226 monitor->mh_backup_path = mutt_str_dup(info->path);
227
228 Monitor = monitor;
229
230 return monitor;
231}
232
237static void monitor_info_free(struct MonitorInfo *info)
238{
239 buf_dealloc(&info->path_buf);
240}
241
246static void monitor_delete(struct Monitor *monitor)
247{
248 if (!monitor)
249 return;
250
251 struct Monitor **ptr = &Monitor;
252
253 while (true)
254 {
255 if (!*ptr)
256 return;
257 if (*ptr == monitor)
258 break;
259 ptr = &(*ptr)->next;
260 }
261
262 FREE(&monitor->mh_backup_path);
263 monitor = monitor->next;
264 FREE(ptr);
265 *ptr = monitor;
266}
267
275{
276 int new_desc = -1;
277 struct Monitor *iter = Monitor;
278 struct stat st = { 0 };
279
280 while (iter && (iter->desc != desc))
281 iter = iter->next;
282
283 if (iter)
284 {
285 if ((iter->type == MUTT_MH) && (stat(iter->mh_backup_path, &st) == 0))
286 {
287 new_desc = inotify_add_watch(INotifyFd, iter->mh_backup_path, INOTIFY_MASK_FILE);
288 if (new_desc == -1)
289 {
290 mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
291 iter->mh_backup_path, errno, strerror(errno));
292 }
293 else
294 {
295 mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n",
296 desc, iter->mh_backup_path);
297 iter->st_dev = st.st_dev;
298 iter->st_ino = st.st_ino;
299 iter->desc = new_desc;
300 }
301 }
302 else
303 {
304 mutt_debug(LL_DEBUG3, "cleanup watch (implicitly removed) - descriptor=%d\n", desc);
305 }
306
307 if (MonitorCurMboxDescriptor == desc)
308 MonitorCurMboxDescriptor = new_desc;
309
310 if (new_desc == -1)
311 {
312 monitor_delete(iter);
314 }
315 }
316
317 return new_desc;
318}
319
332static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
333{
334 char *fmt = NULL;
335 struct stat st = { 0 };
336
337 struct Mailbox *m_cur = get_current_mailbox();
338 if (m)
339 {
340 info->type = m->type;
341 info->path = m->realpath;
342 }
343 else if (m_cur)
344 {
345 info->type = m_cur->type;
346 info->path = m_cur->realpath;
347 }
348 else
349 {
351 }
352
353 if (info->type == MUTT_UNKNOWN)
354 {
356 }
357 else if (info->type == MUTT_MAILDIR)
358 {
359 info->is_dir = true;
360 fmt = "%s/new";
361 }
362 else
363 {
364 info->is_dir = false;
365 if (info->type == MUTT_MH)
366 fmt = "%s/.mh_sequences";
367 }
368 if (fmt)
369 {
370 buf_printf(&info->path_buf, fmt, info->path);
371 info->path = buf_string(&info->path_buf);
372 }
373 if (stat(info->path, &st) != 0)
375
376 struct Monitor *iter = Monitor;
377 while (iter && ((iter->st_ino != st.st_ino) || (iter->st_dev != st.st_dev)))
378 iter = iter->next;
379
380 info->st_dev = st.st_dev;
381 info->st_ino = st.st_ino;
382 info->monitor = iter;
383
385}
386
402{
403 int rc = 0;
404 char buf[EVENT_BUFLEN]
405 __attribute__((aligned(__alignof__(struct inotify_event)))) = { 0 };
406
407 MonitorFilesChanged = false;
408
409 if (INotifyFd != -1)
410 {
411 int fds = poll(PollFds, PollFdsCount, 1000); // 1 Second
412
413 if (fds == -1)
414 {
415 rc = -1;
416 if (errno != EINTR)
417 {
418 mutt_debug(LL_DEBUG2, "poll() failed, errno=%d %s\n", errno, strerror(errno));
419 }
420 }
421 else
422 {
423 bool input_ready = false;
424 for (int i = 0; fds && (i < PollFdsCount); i++)
425 {
426 if (PollFds[i].revents)
427 {
428 fds--;
429 if (PollFds[i].fd == 0)
430 {
431 input_ready = true;
432 }
433 else if (PollFds[i].fd == INotifyFd)
434 {
435 MonitorFilesChanged = true;
436 mutt_debug(LL_DEBUG3, "file change(s) detected\n");
437 char *ptr = buf;
438 const struct inotify_event *event = NULL;
439
440 while (true)
441 {
442 int len = read(INotifyFd, buf, sizeof(buf));
443 if (len == -1)
444 {
445 if (errno != EAGAIN)
446 {
447 mutt_debug(LL_DEBUG2, "read inotify events failed, errno=%d %s\n",
448 errno, strerror(errno));
449 }
450 break;
451 }
452
453 while (ptr < (buf + len))
454 {
455 event = (const struct inotify_event *) ptr;
456 mutt_debug(LL_DEBUG3, "+ detail: descriptor=%d mask=0x%x\n",
457 event->wd, event->mask);
458 if (event->mask & IN_IGNORED)
459 monitor_handle_ignore(event->wd);
460 else if (event->wd == MonitorCurMboxDescriptor)
462 ptr += sizeof(struct inotify_event) + event->len;
463 }
464 }
465 }
466 }
467 }
468 if (!input_ready)
469 rc = MonitorFilesChanged ? -2 : -3;
470 }
471 }
472
473 return rc;
474}
475
485{
486 struct MonitorInfo info = { 0 };
487
488 int rc = 0;
489 enum ResolveResult desc = monitor_resolve(&info, m);
490 if (desc != RESOLVE_RES_OK_NOTEXISTING)
491 {
492 if (!m && (desc == RESOLVE_RES_OK_EXISTING))
494 rc = (desc == RESOLVE_RES_OK_EXISTING) ? 0 : -1;
495 goto cleanup;
496 }
497
498 uint32_t mask = info.is_dir ? INOTIFY_MASK_DIR : INOTIFY_MASK_FILE;
499 if (((INotifyFd == -1) && (monitor_init() == -1)) ||
500 ((desc = inotify_add_watch(INotifyFd, info.path, mask)) == -1))
501 {
502 mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
503 info.path, errno, strerror(errno));
504 rc = -1;
505 goto cleanup;
506 }
507
508 mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n", desc, info.path);
509 if (!m)
511
512 monitor_new(&info, desc);
513
514cleanup:
515 monitor_info_free(&info);
516 return rc;
517}
518
529{
530 struct MonitorInfo info = { 0 };
531 struct MonitorInfo info2 = { 0 };
532 int rc = 0;
533
534 if (!m)
535 {
537 MonitorCurMboxChanged = 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}
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:161
void buf_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:377
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:96
Convenience wrapper for the core headers.
struct Mailbox * mailbox_find(const char *path)
Find the mailbox with a given path.
Definition: mailbox.c:150
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
#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:715
@ LL_DEBUG3
Log at debug level 3.
Definition: logging2.h:45
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:51
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:115
#define FREE(x)
Definition: memory.h:45
#define INOTIFY_MASK_DIR
Definition: monitor.c:70
int mutt_monitor_add(struct Mailbox *m)
Add a watch for a mailbox.
Definition: monitor.c:484
static void monitor_delete(struct Monitor *monitor)
Free a file monitor.
Definition: monitor.c:246
static int mutt_poll_fd_remove(int fd)
Remove a file from the watch list.
Definition: monitor.c:148
static int INotifyFd
Inotify file descriptor.
Definition: monitor.c:58
static int monitor_handle_ignore(int desc)
Listen for when a backup file is closed.
Definition: monitor.c:274
static int MonitorCurMboxDescriptor
Monitor file descriptor of the current mailbox.
Definition: monitor.c:68
static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
Get the monitor for a mailbox.
Definition: monitor.c:332
static struct Monitor * monitor_new(struct MonitorInfo *info, int descriptor)
Create a new file monitor.
Definition: monitor.c:217
int mutt_monitor_poll(void)
Check for filesystem changes.
Definition: monitor.c:401
static size_t PollFdsCount
Number of used entries in the PollFds array.
Definition: monitor.c:62
static void mutt_poll_fd_add(int fd, short events)
Add a file to the watch list.
Definition: monitor.c:119
ResolveResult
Results for the Monitor functions.
Definition: monitor.c:79
@ RESOLVE_RES_FAIL_NOTYPE
Can't identify Mailbox type.
Definition: monitor.c:81
@ RESOLVE_RES_FAIL_STAT
Can't stat() the Mailbox file.
Definition: monitor.c:82
@ RESOLVE_RES_OK_NOTEXISTING
File exists, no monitor is attached.
Definition: monitor.c:83
@ RESOLVE_RES_FAIL_NOMAILBOX
No Mailbox to work on.
Definition: monitor.c:80
@ RESOLVE_RES_OK_EXISTING
File exists, monitor is already attached.
Definition: monitor.c:84
bool MonitorFilesChanged
Set to true when a monitored file has changed.
Definition: monitor.c:53
static int monitor_init(void)
Set up file monitoring.
Definition: monitor.c:168
bool MonitorCurMboxChanged
Set to true when the current mailbox has changed.
Definition: monitor.c:55
static void monitor_check_cleanup(void)
Close down file monitoring.
Definition: monitor.c:200
#define INOTIFY_MASK_FILE
Definition: monitor.c:71
#define EVENT_BUFLEN
Definition: monitor.c:73
static void monitor_info_free(struct MonitorInfo *info)
Shutdown a file monitor.
Definition: monitor.c:237
int mutt_monitor_remove(struct Mailbox *m)
Remove a watch for a mailbox.
Definition: monitor.c:528
static size_t PollFdsLen
Size of PollFds array.
Definition: monitor.c:64
static struct pollfd * PollFds
Array of monitored file descriptors.
Definition: monitor.c:66
Monitor files for changes.
Convenience wrapper for the library headers.
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:253
String manipulation buffer.
Definition: buffer.h:36
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:104
dev_t st_dev
Definition: monitor.c:108
enum MailboxType type
Definition: monitor.c:105
struct Monitor * monitor
Definition: monitor.c:110
bool is_dir
Definition: monitor.c:106
ino_t st_ino
Definition: monitor.c:109
const char * path
Definition: monitor.c:107
struct Buffer path_buf
access via path only (maybe not initialized)
Definition: monitor.c:111
A watch on a file.
Definition: monitor.c:91
struct Monitor * next
Linked list.
Definition: monitor.c:92
ino_t st_ino
Definition: monitor.c:95
dev_t st_dev
Definition: monitor.c:94
int desc
Definition: monitor.c:97
enum MailboxType type
Definition: monitor.c:96
char * mh_backup_path
Definition: monitor.c:93