NeoMutt  2020-11-20
Teaching an old dog new tricks
DOXYGEN
monitor.c
Go to the documentation of this file.
1 
29 #include "config.h"
30 #include <errno.h>
31 #include <limits.h>
32 #include <poll.h>
33 #include <stdbool.h>
34 #include <stdint.h>
35 #include <stdio.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 "gui/lib.h"
43 #include "monitor.h"
44 #include "context.h"
45 #include "mutt_globals.h"
46 #ifndef HAVE_INOTIFY_INIT1
47 #include <fcntl.h>
48 #endif
49 
50 bool MonitorFilesChanged = false;
51 bool MonitorContextChanged = false;
52 
53 static int INotifyFd = -1;
54 static struct Monitor *Monitor = NULL;
55 static size_t PollFdsCount = 0;
56 static size_t PollFdsLen = 0;
57 static struct pollfd *PollFds = NULL;
58 
59 static int MonitorContextDescriptor = -1;
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 
81 struct 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;
101  struct Monitor *monitor;
102  struct Buffer path_buf;
103 };
104 
110 static 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  {
118  if (PollFdsCount == PollFdsLen)
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 
137 static 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 
157 static int monitor_init(void)
158 {
159  if (INotifyFd == -1)
160  {
161 #ifdef HAVE_INOTIFY_INIT1
162  INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
163  if (INotifyFd == -1)
164  {
165  mutt_debug(LL_DEBUG2, "inotify_init1 failed, errno=%d %s\n", errno, strerror(errno));
166  return -1;
167  }
168 #else
169  INotifyFd = inotify_init();
170  if (INotifyFd == -1)
171  {
172  mutt_debug(LL_DEBUG2, "monitor: inotify_init failed, errno=%d %s\n",
173  errno, strerror(errno));
174  return -1;
175  }
176  fcntl(INotifyFd, F_SETFL, O_NONBLOCK);
177  fcntl(INotifyFd, F_SETFD, FD_CLOEXEC);
178 #endif
179  mutt_poll_fd_add(0, POLLIN);
180  mutt_poll_fd_add(INotifyFd, POLLIN);
181  }
182  return 0;
183 }
184 
188 static void monitor_check_free(void)
189 {
190  if (!Monitor && (INotifyFd != -1))
191  {
193  close(INotifyFd);
194  INotifyFd = -1;
195  MonitorFilesChanged = false;
196  }
197 }
198 
205 static struct Monitor *monitor_new(struct MonitorInfo *info, int descriptor)
206 {
207  struct Monitor *monitor = mutt_mem_calloc(1, sizeof(struct Monitor));
208  monitor->type = info->type;
209  monitor->st_dev = info->st_dev;
210  monitor->st_ino = info->st_ino;
211  monitor->desc = descriptor;
212  monitor->next = Monitor;
213  if (info->type == MUTT_MH)
214  monitor->mh_backup_path = mutt_str_dup(info->path);
215 
216  Monitor = monitor;
217 
218  return monitor;
219 }
220 
225 static void monitor_info_init(struct MonitorInfo *info)
226 {
227  memset(info, 0, sizeof(*info));
228 }
229 
234 static void monitor_info_free(struct MonitorInfo *info)
235 {
237 }
238 
243 static void monitor_delete(struct Monitor *monitor)
244 {
245  if (!monitor)
246  return;
247 
248  struct Monitor **ptr = &Monitor;
249 
250  while (true)
251  {
252  if (!*ptr)
253  return;
254  if (*ptr == monitor)
255  break;
256  ptr = &(*ptr)->next;
257  }
258 
259  FREE(&monitor->mh_backup_path);
260  monitor = monitor->next;
261  FREE(ptr);
262  *ptr = monitor;
263 }
264 
272 {
273  int new_desc = -1;
274  struct Monitor *iter = Monitor;
275  struct stat sb;
276 
277  while (iter && (iter->desc != desc))
278  iter = iter->next;
279 
280  if (iter)
281  {
282  if ((iter->type == MUTT_MH) && (stat(iter->mh_backup_path, &sb) == 0))
283  {
284  new_desc = inotify_add_watch(INotifyFd, iter->mh_backup_path, INOTIFY_MASK_FILE);
285  if (new_desc == -1)
286  {
287  mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
288  iter->mh_backup_path, errno, strerror(errno));
289  }
290  else
291  {
292  mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n",
293  desc, iter->mh_backup_path);
294  iter->st_dev = sb.st_dev;
295  iter->st_ino = sb.st_ino;
296  iter->desc = new_desc;
297  }
298  }
299  else
300  {
301  mutt_debug(LL_DEBUG3, "cleanup watch (implicitly removed) - descriptor=%d\n", desc);
302  }
303 
304  if (MonitorContextDescriptor == desc)
305  MonitorContextDescriptor = new_desc;
306 
307  if (new_desc == -1)
308  {
309  monitor_delete(iter);
311  }
312  }
313 
314  return new_desc;
315 }
316 
329 static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
330 {
331  char *fmt = NULL;
332  struct stat sb;
333 
334  if (m)
335  {
336  info->type = m->type;
337  info->path = m->realpath;
338  }
339  else if (Context && Context->mailbox)
340  {
341  info->type = Context->mailbox->type;
342  info->path = Context->mailbox->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  mutt_buffer_printf(&info->path_buf, fmt, info->path);
367  info->path = mutt_b2s(&info->path_buf);
368  }
369  if (stat(info->path, &sb) != 0)
370  return RESOLVE_RES_FAIL_STAT;
371 
372  struct Monitor *iter = Monitor;
373  while (iter && ((iter->st_ino != sb.st_ino) || (iter->st_dev != sb.st_dev)))
374  iter = iter->next;
375 
376  info->st_dev = sb.st_dev;
377  info->st_ino = sb.st_ino;
378  info->monitor = iter;
379 
381 }
382 
398 {
399  int rc = 0;
400  char buf[EVENT_BUFLEN] __attribute__((aligned(__alignof__(struct inotify_event))));
401 
402  MonitorFilesChanged = false;
403 
404  if (INotifyFd != -1)
405  {
406  int fds = poll(PollFds, PollFdsLen, MuttGetchTimeout);
407 
408  if (fds == -1)
409  {
410  rc = -1;
411  if (errno != EINTR)
412  {
413  mutt_debug(LL_DEBUG2, "poll() failed, errno=%d %s\n", errno, strerror(errno));
414  }
415  }
416  else
417  {
418  bool input_ready = false;
419  for (int i = 0; fds && (i < PollFdsCount); i++)
420  {
421  if (PollFds[i].revents)
422  {
423  fds--;
424  if (PollFds[i].fd == 0)
425  {
426  input_ready = true;
427  }
428  else if (PollFds[i].fd == INotifyFd)
429  {
430  MonitorFilesChanged = true;
431  mutt_debug(LL_DEBUG3, "file change(s) detected\n");
432  char *ptr = buf;
433  const struct inotify_event *event = NULL;
434 
435  while (true)
436  {
437  int len = read(INotifyFd, buf, sizeof(buf));
438  if (len == -1)
439  {
440  if (errno != EAGAIN)
441  {
442  mutt_debug(LL_DEBUG2, "read inotify events failed, errno=%d %s\n",
443  errno, strerror(errno));
444  }
445  break;
446  }
447 
448  while (ptr < (buf + len))
449  {
450  event = (const struct inotify_event *) ptr;
451  mutt_debug(LL_DEBUG3, "+ detail: descriptor=%d mask=0x%x\n",
452  event->wd, event->mask);
453  if (event->mask & IN_IGNORED)
454  monitor_handle_ignore(event->wd);
455  else if (event->wd == MonitorContextDescriptor)
456  MonitorContextChanged = true;
457  ptr += sizeof(struct inotify_event) + event->len;
458  }
459  }
460  }
461  }
462  }
463  if (!input_ready)
464  rc = MonitorFilesChanged ? -2 : -3;
465  }
466  }
467 
468  return rc;
469 }
470 
479 int mutt_monitor_add(struct Mailbox *m)
480 {
481  struct MonitorInfo info;
482  monitor_info_init(&info);
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 
510 cleanup:
511  monitor_info_free(&info);
512  return rc;
513 }
514 
525 {
526  struct MonitorInfo info, info2;
527  int rc = 0;
528 
529  monitor_info_init(&info);
530  monitor_info_init(&info2);
531 
532  if (!m)
533  {
535  MonitorContextChanged = false;
536  }
537 
538  if (monitor_resolve(&info, m) != RESOLVE_RES_OK_EXISTING)
539  {
540  rc = 2;
541  goto cleanup;
542  }
543 
544  if (Context && Context->mailbox)
545  {
546  if (m)
547  {
548  if ((monitor_resolve(&info2, NULL) == RESOLVE_RES_OK_EXISTING) &&
549  (info.st_ino == info2.st_ino) && (info.st_dev == info2.st_dev))
550  {
551  rc = 1;
552  goto cleanup;
553  }
554  }
555  else
556  {
558  {
559  rc = 1;
560  goto cleanup;
561  }
562  }
563  }
564 
565  inotify_rm_watch(info.monitor->desc, INotifyFd);
566  mutt_debug(LL_DEBUG3, "inotify_rm_watch for '%s' descriptor=%d\n", info.path,
567  info.monitor->desc);
568 
569  monitor_delete(info.monitor);
571 
572 cleanup:
573  monitor_info_free(&info);
574  monitor_info_free(&info2);
575  return rc;
576 }
Convenience wrapper for the gui headers.
#define INOTIFY_MASK_FILE
Definition: monitor.c:62
The "current" mailbox.
Definition: context.h:38
#define EVENT_BUFLEN
Definition: monitor.c:64
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
static size_t PollFdsLen
Definition: monitor.c:56
static struct pollfd * PollFds
Definition: monitor.c:57
ino_t st_ino
Definition: monitor.c:86
Information about a monitored file.
Definition: monitor.c:94
The "currently-open" mailbox.
struct Buffer path_buf
access via path only (maybe not initialized)
Definition: monitor.c:102
int desc
Definition: monitor.c:88
static void monitor_delete(struct Monitor *monitor)
Free a file monitor.
Definition: monitor.c:243
struct Mailbox * mailbox_find(const char *path)
Find the mailbox with a given path.
Definition: mailbox.c:94
int mutt_monitor_remove(struct Mailbox *m)
Remove a watch for a mailbox.
Definition: monitor.c:524
char * realpath
Used for duplicate detection, context comparison, and the sidebar.
Definition: mailbox.h:84
String manipulation buffer.
Definition: buffer.h:33
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
Mailbox wasn&#39;t recognised.
Definition: mailbox.h:47
struct Monitor * next
Definition: monitor.c:83
File exists, monitor is already attached.
Definition: monitor.c:75
bool MonitorFilesChanged
true after a monitored file has changed
Definition: monitor.c:50
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
static int monitor_init(void)
Set up file monitoring.
Definition: monitor.c:157
static int INotifyFd
Definition: monitor.c:53
int mutt_monitor_add(struct Mailbox *m)
Add a watch for a mailbox.
Definition: monitor.c:479
bool is_dir
Definition: monitor.c:97
struct Mailbox * mailbox
Definition: context.h:50
Log at debug level 2.
Definition: logging.h:41
enum MailboxType type
Definition: monitor.c:96
ResolveResult
Results for the Monitor functions.
Definition: monitor.c:69
Convenience wrapper for the core headers.
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:294
int mutt_monitor_poll(void)
Check for filesystem changes.
Definition: monitor.c:397
&#39;Maildir&#39; Mailbox type
Definition: mailbox.h:51
char * mh_backup_path
Definition: monitor.c:84
No Mailbox to work on.
Definition: monitor.c:71
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
Can&#39;t identify Mailbox type.
Definition: monitor.c:72
struct Monitor * monitor
Definition: monitor.c:101
#define mutt_b2s(buf)
Definition: buffer.h:41
A mailbox.
Definition: mailbox.h:81
static int mutt_poll_fd_remove(int fd)
Remove a file from the watch list.
Definition: monitor.c:137
#define INOTIFY_MASK_DIR
Definition: monitor.c:61
static struct Monitor * Monitor
Definition: monitor.c:54
&#39;MH&#39; Mailbox type
Definition: mailbox.h:50
dev_t st_dev
Definition: monitor.c:85
enum MailboxType type
Definition: monitor.c:87
static void mutt_poll_fd_add(int fd, short events)
Add a file to the watch list.
Definition: monitor.c:110
MailboxType
Supported mailbox formats.
Definition: mailbox.h:43
Can&#39;t stat() the Mailbox file.
Definition: monitor.c:73
File exists, no monitor is attached.
Definition: monitor.c:74
static void monitor_info_init(struct MonitorInfo *info)
Set up a file monitor.
Definition: monitor.c:225
bool MonitorContextChanged
true after the current mailbox has changed
Definition: monitor.c:51
const char * path
Definition: monitor.c:98
dev_t st_dev
Definition: monitor.c:99
static void monitor_check_free(void)
Close down file monitoring.
Definition: monitor.c:188
#define FREE(x)
Definition: memory.h:40
Monitor files for changes.
static int MonitorContextDescriptor
Definition: monitor.c:59
Hundreds of global variables to back the user variables.
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
Convenience wrapper for the library headers.
static size_t PollFdsCount
Definition: monitor.c:55
static struct Monitor * monitor_new(struct MonitorInfo *info, int descriptor)
Create a new file monitor.
Definition: monitor.c:205
static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
Get the monitor for a mailbox.
Definition: monitor.c:329
static void monitor_info_free(struct MonitorInfo *info)
Shutdown a file monitor.
Definition: monitor.c:234
Log at debug level 3.
Definition: logging.h:42
int MuttGetchTimeout
Timeout in ms for mutt_getch()
Definition: curs_lib.c:93
static int monitor_handle_ignore(int desc)
Listen for when a backup file is closed.
Definition: monitor.c:271
ino_t st_ino
Definition: monitor.c:100