NeoMutt  2024-04-16-36-g75b6fb
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 <unistd.h>
43#include "mutt/lib.h"
44#include "core/lib.h"
45#include "monitor.h"
46#include "index/lib.h"
47#ifndef HAVE_INOTIFY_INIT1
48#include <fcntl.h>
49#endif
50
55
57static int INotifyFd = -1;
59static struct Monitor *Monitor = NULL;
61static size_t PollFdsCount = 0;
63static size_t PollFdsLen = 0;
65static struct pollfd *PollFds = NULL;
68
69#define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR)
70#define INOTIFY_MASK_FILE IN_CLOSE_WRITE
71
72#define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1)
73
78{
84};
85
89struct Monitor
90{
91 struct Monitor *next;
93 dev_t st_dev;
94 ino_t st_ino;
96 int desc;
97};
98
103{
105 bool is_dir;
106 const char *path;
107 dev_t st_dev;
108 ino_t st_ino;
111};
112
118static void mutt_poll_fd_add(int fd, short events)
119{
120 int i = 0;
121 for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
122 ; // do nothing
123
124 if (i == PollFdsCount)
125 {
127 {
128 PollFdsLen += 2;
129 mutt_mem_realloc(&PollFds, PollFdsLen * sizeof(struct pollfd));
130 }
131 PollFdsCount++;
132 PollFds[i].fd = fd;
133 PollFds[i].events = events;
134 }
135 else
136 {
137 PollFds[i].events |= events;
138 }
139}
140
147static int mutt_poll_fd_remove(int fd)
148{
149 int i = 0;
150 for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
151 ; // do nothing
152
153 if (i == PollFdsCount)
154 return -1;
155 int d = PollFdsCount - i - 1;
156 if (d != 0)
157 memmove(&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd));
158 PollFdsCount--;
159 return 0;
160}
161
167static int monitor_init(void)
168{
169 if (INotifyFd != -1)
170 return 0;
171
172#ifdef HAVE_INOTIFY_INIT1
173 INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
174 if (INotifyFd == -1)
175 {
176 mutt_debug(LL_DEBUG2, "inotify_init1 failed, errno=%d %s\n", errno, strerror(errno));
177 return -1;
178 }
179#else
180 INotifyFd = inotify_init();
181 if (INotifyFd == -1)
182 {
183 mutt_debug(LL_DEBUG2, "monitor: inotify_init failed, errno=%d %s\n", errno,
184 strerror(errno));
185 return -1;
186 }
187 fcntl(INotifyFd, F_SETFL, O_NONBLOCK);
188 fcntl(INotifyFd, F_SETFD, FD_CLOEXEC);
189#endif
190 mutt_poll_fd_add(0, POLLIN);
192
193 return 0;
194}
195
199static void monitor_check_cleanup(void)
200{
201 if (!Monitor && (INotifyFd != -1))
202 {
204 close(INotifyFd);
205 INotifyFd = -1;
206 MonitorFilesChanged = false;
207 }
208}
209
216static struct Monitor *monitor_new(struct MonitorInfo *info, int descriptor)
217{
218 struct Monitor *monitor = mutt_mem_calloc(1, sizeof(struct Monitor));
219 monitor->type = info->type;
220 monitor->st_dev = info->st_dev;
221 monitor->st_ino = info->st_ino;
222 monitor->desc = descriptor;
223 monitor->next = Monitor;
224 if (info->type == MUTT_MH)
225 monitor->mh_backup_path = mutt_str_dup(info->path);
226
227 Monitor = monitor;
228
229 return monitor;
230}
231
236static void monitor_info_free(struct MonitorInfo *info)
237{
238 buf_dealloc(&info->path_buf);
239}
240
245static void monitor_delete(struct Monitor *monitor)
246{
247 if (!monitor)
248 return;
249
250 struct Monitor **ptr = &Monitor;
251
252 while (true)
253 {
254 if (!*ptr)
255 return;
256 if (*ptr == monitor)
257 break;
258 ptr = &(*ptr)->next;
259 }
260
261 FREE(&monitor->mh_backup_path);
262 monitor = monitor->next;
263 FREE(ptr);
264 *ptr = monitor;
265}
266
274{
275 int new_desc = -1;
276 struct Monitor *iter = Monitor;
277 struct stat st = { 0 };
278
279 while (iter && (iter->desc != desc))
280 iter = iter->next;
281
282 if (iter)
283 {
284 if ((iter->type == MUTT_MH) && (stat(iter->mh_backup_path, &st) == 0))
285 {
286 new_desc = inotify_add_watch(INotifyFd, iter->mh_backup_path, INOTIFY_MASK_FILE);
287 if (new_desc == -1)
288 {
289 mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
290 iter->mh_backup_path, errno, strerror(errno));
291 }
292 else
293 {
294 mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n",
295 desc, iter->mh_backup_path);
296 iter->st_dev = st.st_dev;
297 iter->st_ino = st.st_ino;
298 iter->desc = new_desc;
299 }
300 }
301 else
302 {
303 mutt_debug(LL_DEBUG3, "cleanup watch (implicitly removed) - descriptor=%d\n", desc);
304 }
305
306 if (MonitorCurMboxDescriptor == desc)
307 MonitorCurMboxDescriptor = new_desc;
308
309 if (new_desc == -1)
310 {
311 monitor_delete(iter);
313 }
314 }
315
316 return new_desc;
317}
318
331static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
332{
333 char *fmt = NULL;
334 struct stat st = { 0 };
335
336 struct Mailbox *m_cur = get_current_mailbox();
337 if (m)
338 {
339 info->type = m->type;
340 info->path = m->realpath;
341 }
342 else if (m_cur)
343 {
344 info->type = m_cur->type;
345 info->path = m_cur->realpath;
346 }
347 else
348 {
350 }
351
352 if (info->type == MUTT_UNKNOWN)
353 {
355 }
356 else if (info->type == MUTT_MAILDIR)
357 {
358 info->is_dir = true;
359 fmt = "%s/new";
360 }
361 else
362 {
363 info->is_dir = false;
364 if (info->type == MUTT_MH)
365 fmt = "%s/.mh_sequences";
366 }
367 if (fmt)
368 {
369 buf_printf(&info->path_buf, fmt, info->path);
370 info->path = buf_string(&info->path_buf);
371 }
372 if (stat(info->path, &st) != 0)
374
375 struct Monitor *iter = Monitor;
376 while (iter && ((iter->st_ino != st.st_ino) || (iter->st_dev != st.st_dev)))
377 iter = iter->next;
378
379 info->st_dev = st.st_dev;
380 info->st_ino = st.st_ino;
381 info->monitor = iter;
382
384}
385
401{
402 int rc = 0;
403 char buf[EVENT_BUFLEN]
404 __attribute__((aligned(__alignof__(struct inotify_event)))) = { 0 };
405
406 MonitorFilesChanged = false;
407
408 if (INotifyFd != -1)
409 {
410 int fds = poll(PollFds, PollFdsCount, 1000); // 1 Second
411
412 if (fds == -1)
413 {
414 rc = -1;
415 if (errno != EINTR)
416 {
417 mutt_debug(LL_DEBUG2, "poll() failed, errno=%d %s\n", errno, strerror(errno));
418 }
419 }
420 else
421 {
422 bool input_ready = false;
423 for (int i = 0; fds && (i < PollFdsCount); i++)
424 {
425 if (PollFds[i].revents)
426 {
427 fds--;
428 if (PollFds[i].fd == 0)
429 {
430 input_ready = true;
431 }
432 else if (PollFds[i].fd == INotifyFd)
433 {
434 MonitorFilesChanged = true;
435 mutt_debug(LL_DEBUG3, "file change(s) detected\n");
436 char *ptr = buf;
437 const struct inotify_event *event = NULL;
438
439 while (true)
440 {
441 int len = read(INotifyFd, buf, sizeof(buf));
442 if (len == -1)
443 {
444 if (errno != EAGAIN)
445 {
446 mutt_debug(LL_DEBUG2, "read inotify events failed, errno=%d %s\n",
447 errno, strerror(errno));
448 }
449 break;
450 }
451
452 while (ptr < (buf + len))
453 {
454 event = (const struct inotify_event *) ptr;
455 mutt_debug(LL_DEBUG3, "+ detail: descriptor=%d mask=0x%x\n",
456 event->wd, event->mask);
457 if (event->mask & IN_IGNORED)
458 monitor_handle_ignore(event->wd);
459 else if (event->wd == MonitorCurMboxDescriptor)
461 ptr += sizeof(struct inotify_event) + event->len;
462 }
463 }
464 }
465 }
466 }
467 if (!input_ready)
468 rc = MonitorFilesChanged ? -2 : -3;
469 }
470 }
471
472 return rc;
473}
474
484{
485 struct MonitorInfo info = { 0 };
486
487 int rc = 0;
488 enum ResolveResult desc = monitor_resolve(&info, m);
489 if (desc != RESOLVE_RES_OK_NOTEXISTING)
490 {
491 if (!m && (desc == RESOLVE_RES_OK_EXISTING))
493 rc = (desc == RESOLVE_RES_OK_EXISTING) ? 0 : -1;
494 goto cleanup;
495 }
496
497 uint32_t mask = info.is_dir ? INOTIFY_MASK_DIR : INOTIFY_MASK_FILE;
498 if (((INotifyFd == -1) && (monitor_init() == -1)) ||
499 ((desc = inotify_add_watch(INotifyFd, info.path, mask)) == -1))
500 {
501 mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
502 info.path, errno, strerror(errno));
503 rc = -1;
504 goto cleanup;
505 }
506
507 mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n", desc, info.path);
508 if (!m)
510
511 monitor_new(&info, desc);
512
513cleanup:
514 monitor_info_free(&info);
515 return rc;
516}
517
528{
529 struct MonitorInfo info = { 0 };
530 struct MonitorInfo info2 = { 0 };
531 int rc = 0;
532
533 if (!m)
534 {
536 MonitorCurMboxChanged = false;
537 }
538
540 {
541 rc = 2;
542 goto cleanup;
543 }
544
545 struct Mailbox *m_cur = get_current_mailbox();
546 if (m_cur)
547 {
548 if (m)
549 {
550 if ((monitor_resolve(&info2, NULL) == RESOLVE_RES_OK_EXISTING) &&
551 (info.st_ino == info2.st_ino) && (info.st_dev == info2.st_dev))
552 {
553 rc = 1;
554 goto cleanup;
555 }
556 }
557 else
558 {
559 if (mailbox_find(m_cur->realpath))
560 {
561 rc = 1;
562 goto cleanup;
563 }
564 }
565 }
566
567 inotify_rm_watch(info.monitor->desc, INotifyFd);
568 mutt_debug(LL_DEBUG3, "inotify_rm_watch for '%s' descriptor=%d\n", info.path,
569 info.monitor->desc);
570
573
574cleanup:
575 monitor_info_free(&info);
576 monitor_info_free(&info2);
577 return rc;
578}
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
void buf_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:376
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:151
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: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:69
int mutt_monitor_add(struct Mailbox *m)
Add a watch for a mailbox.
Definition: monitor.c:483
static void monitor_delete(struct Monitor *monitor)
Free a file monitor.
Definition: monitor.c:245
static int mutt_poll_fd_remove(int fd)
Remove a file from the watch list.
Definition: monitor.c:147
static int INotifyFd
Inotify file descriptor.
Definition: monitor.c:57
static int monitor_handle_ignore(int desc)
Listen for when a backup file is closed.
Definition: monitor.c:273
static int MonitorCurMboxDescriptor
Monitor file descriptor of the current mailbox.
Definition: monitor.c:67
static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
Get the monitor for a mailbox.
Definition: monitor.c:331
static struct Monitor * monitor_new(struct MonitorInfo *info, int descriptor)
Create a new file monitor.
Definition: monitor.c:216
int mutt_monitor_poll(void)
Check for filesystem changes.
Definition: monitor.c:400
static size_t PollFdsCount
Number of used entries in the PollFds array.
Definition: monitor.c:61
static void mutt_poll_fd_add(int fd, short events)
Add a file to the watch list.
Definition: monitor.c:118
ResolveResult
Results for the Monitor functions.
Definition: monitor.c:78
@ RESOLVE_RES_FAIL_NOTYPE
Can't identify Mailbox type.
Definition: monitor.c:80
@ RESOLVE_RES_FAIL_STAT
Can't stat() the Mailbox file.
Definition: monitor.c:81
@ RESOLVE_RES_OK_NOTEXISTING
File exists, no monitor is attached.
Definition: monitor.c:82
@ RESOLVE_RES_FAIL_NOMAILBOX
No Mailbox to work on.
Definition: monitor.c:79
@ RESOLVE_RES_OK_EXISTING
File exists, monitor is already attached.
Definition: monitor.c:83
bool MonitorFilesChanged
Set to true when a monitored file has changed.
Definition: monitor.c:52
static int monitor_init(void)
Set up file monitoring.
Definition: monitor.c:167
bool MonitorCurMboxChanged
Set to true when the current mailbox has changed.
Definition: monitor.c:54
static void monitor_check_cleanup(void)
Close down file monitoring.
Definition: monitor.c:199
#define INOTIFY_MASK_FILE
Definition: monitor.c:70
#define EVENT_BUFLEN
Definition: monitor.c:72
static void monitor_info_free(struct MonitorInfo *info)
Shutdown a file monitor.
Definition: monitor.c:236
int mutt_monitor_remove(struct Mailbox *m)
Remove a watch for a mailbox.
Definition: monitor.c:527
static size_t PollFdsLen
Size of PollFds array.
Definition: monitor.c:63
static struct pollfd * PollFds
Array of monitored file descriptors.
Definition: monitor.c:65
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:103
dev_t st_dev
Definition: monitor.c:107
enum MailboxType type
Definition: monitor.c:104
struct Monitor * monitor
Definition: monitor.c:109
bool is_dir
Definition: monitor.c:105
ino_t st_ino
Definition: monitor.c:108
const char * path
Definition: monitor.c:106
struct Buffer path_buf
access via path only (maybe not initialized)
Definition: monitor.c:110
A watch on a file.
Definition: monitor.c:90
struct Monitor * next
Linked list.
Definition: monitor.c:91
ino_t st_ino
Definition: monitor.c:94
dev_t st_dev
Definition: monitor.c:93
int desc
Definition: monitor.c:96
enum MailboxType type
Definition: monitor.c:95
char * mh_backup_path
Definition: monitor.c:92