NeoMutt  2021-10-29-225-gb9986f
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 #include "context.h"
47 #include "mutt_globals.h"
48 #ifndef HAVE_INOTIFY_INIT1
49 #include <fcntl.h>
50 #endif
51 
52 bool MonitorFilesChanged = false;
53 bool MonitorContextChanged = false;
54 
55 static int INotifyFd = -1;
56 static struct Monitor *Monitor = NULL;
57 static size_t PollFdsCount = 0;
58 static size_t PollFdsLen = 0;
59 static struct pollfd *PollFds = NULL;
60 
61 static int MonitorContextDescriptor = -1;
62 
63 #define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR)
64 #define INOTIFY_MASK_FILE IN_CLOSE_WRITE
65 
66 #define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1)
67 
72 {
78 };
79 
83 struct Monitor
84 {
85  struct Monitor *next;
87  dev_t st_dev;
88  ino_t st_ino;
89  enum MailboxType type;
90  int desc;
91 };
92 
97 {
98  enum MailboxType type;
99  bool is_dir;
100  const char *path;
101  dev_t st_dev;
102  ino_t st_ino;
103  struct Monitor *monitor;
104  struct Buffer path_buf;
105 };
106 
112 static void mutt_poll_fd_add(int fd, short events)
113 {
114  int i = 0;
115  for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
116  ; // do nothing
117 
118  if (i == PollFdsCount)
119  {
120  if (PollFdsCount == PollFdsLen)
121  {
122  PollFdsLen += 2;
123  mutt_mem_realloc(&PollFds, PollFdsLen * sizeof(struct pollfd));
124  }
125  PollFdsCount++;
126  PollFds[i].fd = fd;
127  PollFds[i].events = events;
128  }
129  else
130  PollFds[i].events |= events;
131 }
132 
139 static int mutt_poll_fd_remove(int fd)
140 {
141  int i = 0;
142  for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
143  ; // do nothing
144 
145  if (i == PollFdsCount)
146  return -1;
147  int d = PollFdsCount - i - 1;
148  if (d != 0)
149  memmove(&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd));
150  PollFdsCount--;
151  return 0;
152 }
153 
159 static int monitor_init(void)
160 {
161  if (INotifyFd != -1)
162  return 0;
163 
164 #ifdef HAVE_INOTIFY_INIT1
165  INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
166  if (INotifyFd == -1)
167  {
168  mutt_debug(LL_DEBUG2, "inotify_init1 failed, errno=%d %s\n", errno, strerror(errno));
169  return -1;
170  }
171 #else
172  INotifyFd = inotify_init();
173  if (INotifyFd == -1)
174  {
175  mutt_debug(LL_DEBUG2, "monitor: inotify_init failed, errno=%d %s\n", errno,
176  strerror(errno));
177  return -1;
178  }
179  fcntl(INotifyFd, F_SETFL, O_NONBLOCK);
180  fcntl(INotifyFd, F_SETFD, FD_CLOEXEC);
181 #endif
182  mutt_poll_fd_add(0, POLLIN);
183  mutt_poll_fd_add(INotifyFd, POLLIN);
184 
185  return 0;
186 }
187 
191 static void monitor_check_free(void)
192 {
193  if (!Monitor && (INotifyFd != -1))
194  {
196  close(INotifyFd);
197  INotifyFd = -1;
198  MonitorFilesChanged = false;
199  }
200 }
201 
208 static struct Monitor *monitor_new(struct MonitorInfo *info, int descriptor)
209 {
210  struct Monitor *monitor = mutt_mem_calloc(1, sizeof(struct Monitor));
211  monitor->type = info->type;
212  monitor->st_dev = info->st_dev;
213  monitor->st_ino = info->st_ino;
214  monitor->desc = descriptor;
215  monitor->next = Monitor;
216  if (info->type == MUTT_MH)
217  monitor->mh_backup_path = mutt_str_dup(info->path);
218 
219  Monitor = monitor;
220 
221  return monitor;
222 }
223 
228 static void monitor_info_init(struct MonitorInfo *info)
229 {
230  memset(info, 0, sizeof(*info));
231 }
232 
237 static void monitor_info_free(struct MonitorInfo *info)
238 {
240 }
241 
246 static 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 (MonitorContextDescriptor == desc)
308  MonitorContextDescriptor = new_desc;
309 
310  if (new_desc == -1)
311  {
312  monitor_delete(iter);
314  }
315  }
316 
317  return new_desc;
318 }
319 
332 static 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  mutt_buffer_printf(&info->path_buf, fmt, info->path);
371  info->path = mutt_buffer_string(&info->path_buf);
372  }
373  if (stat(info->path, &st) != 0)
374  return RESOLVE_RES_FAIL_STAT;
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] __attribute__((aligned(__alignof__(struct inotify_event))));
405 
406  MonitorFilesChanged = false;
407 
408  if (INotifyFd != -1)
409  {
410  int fds = poll(PollFds, PollFdsCount, MuttGetchTimeout);
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 == MonitorContextDescriptor)
460  MonitorContextChanged = true;
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 
483 int mutt_monitor_add(struct Mailbox *m)
484 {
485  struct MonitorInfo info;
486  monitor_info_init(&info);
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 
514 cleanup:
515  monitor_info_free(&info);
516  return rc;
517 }
518 
529 {
530  struct MonitorInfo info, info2;
531  int rc = 0;
532 
533  monitor_info_init(&info);
534  monitor_info_init(&info2);
535 
536  if (!m)
537  {
539  MonitorContextChanged = false;
540  }
541 
542  if (monitor_resolve(&info, m) != RESOLVE_RES_OK_EXISTING)
543  {
544  rc = 2;
545  goto cleanup;
546  }
547 
548  struct Mailbox *m_cur = get_current_mailbox();
549  if (m_cur)
550  {
551  if (m)
552  {
553  if ((monitor_resolve(&info2, NULL) == RESOLVE_RES_OK_EXISTING) &&
554  (info.st_ino == info2.st_ino) && (info.st_dev == info2.st_dev))
555  {
556  rc = 1;
557  goto cleanup;
558  }
559  }
560  else
561  {
562  if (mailbox_find(m_cur->realpath))
563  {
564  rc = 1;
565  goto cleanup;
566  }
567  }
568  }
569 
570  inotify_rm_watch(info.monitor->desc, INotifyFd);
571  mutt_debug(LL_DEBUG3, "inotify_rm_watch for '%s' descriptor=%d\n", info.path,
572  info.monitor->desc);
573 
574  monitor_delete(info.monitor);
576 
577 cleanup:
578  monitor_info_free(&info);
579  monitor_info_free(&info2);
580  return rc;
581 }
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:294
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
static const char * mutt_buffer_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:77
The "currently-open" mailbox.
Convenience wrapper for the core headers.
int MuttGetchTimeout
Timeout in ms for mutt_getch()
Definition: curs_lib.c:83
#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:474
@ 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:128
MailboxType
Supported mailbox formats.
Definition: mailbox.h:44
@ MUTT_MH
'MH' Mailbox type
Definition: mailbox.h:50
@ MUTT_UNKNOWN
Mailbox wasn't recognised.
Definition: mailbox.h:47
@ MUTT_MAILDIR
'Maildir' Mailbox type
Definition: mailbox.h:51
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:40
#define INOTIFY_MASK_DIR
Definition: monitor.c:63
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:246
static int MonitorContextDescriptor
Definition: monitor.c:61
static int mutt_poll_fd_remove(int fd)
Remove a file from the watch list.
Definition: monitor.c:139
static int INotifyFd
Definition: monitor.c:55
static int monitor_handle_ignore(int desc)
Listen for when a backup file is closed.
Definition: monitor.c:274
static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
Get the monitor for a mailbox.
Definition: monitor.c:332
int mutt_monitor_poll(void)
Check for filesystem changes.
Definition: monitor.c:401
static size_t PollFdsCount
Definition: monitor.c:57
static void mutt_poll_fd_add(int fd, short events)
Add a file to the watch list.
Definition: monitor.c:112
ResolveResult
Results for the Monitor functions.
Definition: monitor.c:72
@ RESOLVE_RES_FAIL_NOTYPE
Can't identify Mailbox type.
Definition: monitor.c:74
@ RESOLVE_RES_FAIL_STAT
Can't stat() the Mailbox file.
Definition: monitor.c:75
@ RESOLVE_RES_OK_NOTEXISTING
File exists, no monitor is attached.
Definition: monitor.c:76
@ RESOLVE_RES_FAIL_NOMAILBOX
No Mailbox to work on.
Definition: monitor.c:73
@ RESOLVE_RES_OK_EXISTING
File exists, monitor is already attached.
Definition: monitor.c:77
bool MonitorFilesChanged
true after a monitored file has changed
Definition: monitor.c:52
static void monitor_check_free(void)
Close down file monitoring.
Definition: monitor.c:191
static int monitor_init(void)
Set up file monitoring.
Definition: monitor.c:159
static struct Monitor * Monitor
Definition: monitor.c:56
bool MonitorContextChanged
true after the current mailbox has changed
Definition: monitor.c:53
static void monitor_info_init(struct MonitorInfo *info)
Set up a file monitor.
Definition: monitor.c:228
static struct Monitor * monitor_new(struct MonitorInfo *info, int descriptor)
Create a new file monitor.
Definition: monitor.c:208
#define INOTIFY_MASK_FILE
Definition: monitor.c:64
#define EVENT_BUFLEN
Definition: monitor.c:66
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
Definition: monitor.c:58
static struct pollfd * PollFds
Definition: monitor.c:59
Monitor files for changes.
Convenience wrapper for the library headers.
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:181
Hundreds of global variables to back the user variables.
String manipulation buffer.
Definition: buffer.h:34
A mailbox.
Definition: mailbox.h:82
char * realpath
Used for duplicate detection, context comparison, and the sidebar.
Definition: mailbox.h:84
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
Information about a monitored file.
Definition: monitor.c:97
dev_t st_dev
Definition: monitor.c:101
enum MailboxType type
Definition: monitor.c:98
struct Monitor * monitor
Definition: monitor.c:103
bool is_dir
Definition: monitor.c:99
ino_t st_ino
Definition: monitor.c:102
const char * path
Definition: monitor.c:100
struct Buffer path_buf
access via path only (maybe not initialized)
Definition: monitor.c:104
A watch on a file.
Definition: monitor.c:84
struct Monitor * next
Linked list.
Definition: monitor.c:85
ino_t st_ino
Definition: monitor.c:88
dev_t st_dev
Definition: monitor.c:87
int desc
Definition: monitor.c:90
enum MailboxType type
Definition: monitor.c:89
char * mh_backup_path
Definition: monitor.c:86