NeoMutt  2021-02-05-89-gabe350
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 "context.h"
46 #include "mutt_globals.h"
47 #ifndef HAVE_INOTIFY_INIT1
48 #include <fcntl.h>
49 #endif
50 
51 bool MonitorFilesChanged = false;
52 bool MonitorContextChanged = false;
53 
54 static int INotifyFd = -1;
55 static struct Monitor *Monitor = NULL;
56 static size_t PollFdsCount = 0;
57 static size_t PollFdsLen = 0;
58 static struct pollfd *PollFds = NULL;
59 
60 static int MonitorContextDescriptor = -1;
61 
62 #define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR)
63 #define INOTIFY_MASK_FILE IN_CLOSE_WRITE
64 
65 #define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1)
66 
71 {
77 };
78 
82 struct Monitor
83 {
84  struct Monitor *next;
86  dev_t st_dev;
87  ino_t st_ino;
89  int desc;
90 };
91 
96 {
98  bool is_dir;
99  const char *path;
100  dev_t st_dev;
101  ino_t st_ino;
102  struct Monitor *monitor;
103  struct Buffer path_buf;
104 };
105 
111 static void mutt_poll_fd_add(int fd, short events)
112 {
113  int i = 0;
114  for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
115  ; // do nothing
116 
117  if (i == PollFdsCount)
118  {
119  if (PollFdsCount == PollFdsLen)
120  {
121  PollFdsLen += 2;
122  mutt_mem_realloc(&PollFds, PollFdsLen * sizeof(struct pollfd));
123  }
124  PollFdsCount++;
125  PollFds[i].fd = fd;
126  PollFds[i].events = events;
127  }
128  else
129  PollFds[i].events |= events;
130 }
131 
138 static int mutt_poll_fd_remove(int fd)
139 {
140  int i = 0;
141  for (; (i < PollFdsCount) && (PollFds[i].fd != fd); i++)
142  ; // do nothing
143 
144  if (i == PollFdsCount)
145  return -1;
146  int d = PollFdsCount - i - 1;
147  if (d != 0)
148  memmove(&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd));
149  PollFdsCount--;
150  return 0;
151 }
152 
158 static int monitor_init(void)
159 {
160  if (INotifyFd != -1)
161  return 0;
162 
163 #ifdef HAVE_INOTIFY_INIT1
164  INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
165  if (INotifyFd == -1)
166  {
167  mutt_debug(LL_DEBUG2, "inotify_init1 failed, errno=%d %s\n", errno, strerror(errno));
168  return -1;
169  }
170 #else
171  INotifyFd = inotify_init();
172  if (INotifyFd == -1)
173  {
174  mutt_debug(LL_DEBUG2, "monitor: inotify_init failed, errno=%d %s\n", errno,
175  strerror(errno));
176  return -1;
177  }
178  fcntl(INotifyFd, F_SETFL, O_NONBLOCK);
179  fcntl(INotifyFd, F_SETFD, FD_CLOEXEC);
180 #endif
181  mutt_poll_fd_add(0, POLLIN);
182  mutt_poll_fd_add(INotifyFd, POLLIN);
183 
184  return 0;
185 }
186 
190 static void monitor_check_free(void)
191 {
192  if (!Monitor && (INotifyFd != -1))
193  {
195  close(INotifyFd);
196  INotifyFd = -1;
197  MonitorFilesChanged = false;
198  }
199 }
200 
207 static struct Monitor *monitor_new(struct MonitorInfo *info, int descriptor)
208 {
209  struct Monitor *monitor = mutt_mem_calloc(1, sizeof(struct Monitor));
210  monitor->type = info->type;
211  monitor->st_dev = info->st_dev;
212  monitor->st_ino = info->st_ino;
213  monitor->desc = descriptor;
214  monitor->next = Monitor;
215  if (info->type == MUTT_MH)
216  monitor->mh_backup_path = mutt_str_dup(info->path);
217 
218  Monitor = monitor;
219 
220  return monitor;
221 }
222 
227 static void monitor_info_init(struct MonitorInfo *info)
228 {
229  memset(info, 0, sizeof(*info));
230 }
231 
236 static void monitor_info_free(struct MonitorInfo *info)
237 {
239 }
240 
245 static 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 sb;
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, &sb) == 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 = sb.st_dev;
297  iter->st_ino = sb.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 (MonitorContextDescriptor == desc)
307  MonitorContextDescriptor = new_desc;
308 
309  if (new_desc == -1)
310  {
311  monitor_delete(iter);
313  }
314  }
315 
316  return new_desc;
317 }
318 
331 static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
332 {
333  char *fmt = NULL;
334  struct stat sb;
335 
336  if (m)
337  {
338  info->type = m->type;
339  info->path = m->realpath;
340  }
341  else if (ctx_mailbox(Context))
342  {
343  info->type = Context->mailbox->type;
344  info->path = Context->mailbox->realpath;
345  }
346  else
347  {
349  }
350 
351  if (info->type == MUTT_UNKNOWN)
352  {
354  }
355  else if (info->type == MUTT_MAILDIR)
356  {
357  info->is_dir = true;
358  fmt = "%s/new";
359  }
360  else
361  {
362  info->is_dir = false;
363  if (info->type == MUTT_MH)
364  fmt = "%s/.mh_sequences";
365  }
366  if (fmt)
367  {
368  mutt_buffer_printf(&info->path_buf, fmt, info->path);
369  info->path = mutt_buffer_string(&info->path_buf);
370  }
371  if (stat(info->path, &sb) != 0)
372  return RESOLVE_RES_FAIL_STAT;
373 
374  struct Monitor *iter = Monitor;
375  while (iter && ((iter->st_ino != sb.st_ino) || (iter->st_dev != sb.st_dev)))
376  iter = iter->next;
377 
378  info->st_dev = sb.st_dev;
379  info->st_ino = sb.st_ino;
380  info->monitor = iter;
381 
383 }
384 
400 {
401  int rc = 0;
402  char buf[EVENT_BUFLEN] __attribute__((aligned(__alignof__(struct inotify_event))));
403 
404  MonitorFilesChanged = false;
405 
406  if (INotifyFd != -1)
407  {
408  int fds = poll(PollFds, PollFdsLen, MuttGetchTimeout);
409 
410  if (fds == -1)
411  {
412  rc = -1;
413  if (errno != EINTR)
414  {
415  mutt_debug(LL_DEBUG2, "poll() failed, errno=%d %s\n", errno, strerror(errno));
416  }
417  }
418  else
419  {
420  bool input_ready = false;
421  for (int i = 0; fds && (i < PollFdsCount); i++)
422  {
423  if (PollFds[i].revents)
424  {
425  fds--;
426  if (PollFds[i].fd == 0)
427  {
428  input_ready = true;
429  }
430  else if (PollFds[i].fd == INotifyFd)
431  {
432  MonitorFilesChanged = true;
433  mutt_debug(LL_DEBUG3, "file change(s) detected\n");
434  char *ptr = buf;
435  const struct inotify_event *event = NULL;
436 
437  while (true)
438  {
439  int len = read(INotifyFd, buf, sizeof(buf));
440  if (len == -1)
441  {
442  if (errno != EAGAIN)
443  {
444  mutt_debug(LL_DEBUG2, "read inotify events failed, errno=%d %s\n",
445  errno, strerror(errno));
446  }
447  break;
448  }
449 
450  while (ptr < (buf + len))
451  {
452  event = (const struct inotify_event *) ptr;
453  mutt_debug(LL_DEBUG3, "+ detail: descriptor=%d mask=0x%x\n",
454  event->wd, event->mask);
455  if (event->mask & IN_IGNORED)
456  monitor_handle_ignore(event->wd);
457  else if (event->wd == MonitorContextDescriptor)
458  MonitorContextChanged = true;
459  ptr += sizeof(struct inotify_event) + event->len;
460  }
461  }
462  }
463  }
464  }
465  if (!input_ready)
466  rc = MonitorFilesChanged ? -2 : -3;
467  }
468  }
469 
470  return rc;
471 }
472 
481 int mutt_monitor_add(struct Mailbox *m)
482 {
483  struct MonitorInfo info;
484  monitor_info_init(&info);
485 
486  int rc = 0;
487  enum ResolveResult desc = monitor_resolve(&info, m);
488  if (desc != RESOLVE_RES_OK_NOTEXISTING)
489  {
490  if (!m && (desc == RESOLVE_RES_OK_EXISTING))
492  rc = (desc == RESOLVE_RES_OK_EXISTING) ? 0 : -1;
493  goto cleanup;
494  }
495 
496  uint32_t mask = info.is_dir ? INOTIFY_MASK_DIR : INOTIFY_MASK_FILE;
497  if (((INotifyFd == -1) && (monitor_init() == -1)) ||
498  ((desc = inotify_add_watch(INotifyFd, info.path, mask)) == -1))
499  {
500  mutt_debug(LL_DEBUG2, "inotify_add_watch failed for '%s', errno=%d %s\n",
501  info.path, errno, strerror(errno));
502  rc = -1;
503  goto cleanup;
504  }
505 
506  mutt_debug(LL_DEBUG3, "inotify_add_watch descriptor=%d for '%s'\n", desc, info.path);
507  if (!m)
509 
510  monitor_new(&info, desc);
511 
512 cleanup:
513  monitor_info_free(&info);
514  return rc;
515 }
516 
527 {
528  struct MonitorInfo info, info2;
529  int rc = 0;
530 
531  monitor_info_init(&info);
532  monitor_info_init(&info2);
533 
534  if (!m)
535  {
537  MonitorContextChanged = false;
538  }
539 
540  if (monitor_resolve(&info, m) != RESOLVE_RES_OK_EXISTING)
541  {
542  rc = 2;
543  goto cleanup;
544  }
545 
546  struct Mailbox *m_ctx = ctx_mailbox(Context);
547  if (m_ctx)
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_ctx->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 
572  monitor_delete(info.monitor);
574 
575 cleanup:
576  monitor_info_free(&info);
577  monitor_info_free(&info2);
578  return rc;
579 }
Monitor::desc
int desc
Definition: monitor.c:89
monitor_info_free
static void monitor_info_free(struct MonitorInfo *info)
Shutdown a file monitor.
Definition: monitor.c:236
MonitorFilesChanged
bool MonitorFilesChanged
true after a monitored file has changed
Definition: monitor.c:51
monitor_delete
static void monitor_delete(struct Monitor *monitor)
Free a file monitor.
Definition: monitor.c:245
mutt_mem_calloc
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
Mailbox
A mailbox.
Definition: mailbox.h:81
RESOLVE_RES_OK_NOTEXISTING
@ RESOLVE_RES_OK_NOTEXISTING
File exists, no monitor is attached.
Definition: monitor.c:75
PollFdsLen
static size_t PollFdsLen
Definition: monitor.c:57
PollFds
static struct pollfd * PollFds
Definition: monitor.c:58
MonitorInfo::monitor
struct Monitor * monitor
Definition: monitor.c:102
Buffer
String manipulation buffer.
Definition: buffer.h:33
mutt_monitor_remove
int mutt_monitor_remove(struct Mailbox *m)
Remove a watch for a mailbox.
Definition: monitor.c:526
LL_DEBUG3
@ LL_DEBUG3
Log at debug level 3.
Definition: logging.h:42
INotifyFd
static int INotifyFd
Definition: monitor.c:54
ctx_mailbox
struct Mailbox * ctx_mailbox(struct Context *ctx)
wrapper to get the mailbox in a Context, or NULL
Definition: context.c:429
mutt_buffer_dealloc
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:294
MonitorInfo
Information about a monitored file.
Definition: monitor.c:95
INOTIFY_MASK_FILE
#define INOTIFY_MASK_FILE
Definition: monitor.c:63
monitor.h
monitor_init
static int monitor_init(void)
Set up file monitoring.
Definition: monitor.c:158
Monitor::st_ino
ino_t st_ino
Definition: monitor.c:87
Context
The "current" mailbox.
Definition: context.h:38
mutt_str_dup
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
mutt_globals.h
FREE
#define FREE(x)
Definition: memory.h:40
Monitor::type
enum MailboxType type
Definition: monitor.c:88
mutt_monitor_add
int mutt_monitor_add(struct Mailbox *m)
Add a watch for a mailbox.
Definition: monitor.c:481
MonitorInfo::path
const char * path
Definition: monitor.c:99
MonitorInfo::st_ino
ino_t st_ino
Definition: monitor.c:101
Monitor::st_dev
dev_t st_dev
Definition: monitor.c:86
mutt_monitor_poll
int mutt_monitor_poll(void)
Check for filesystem changes.
Definition: monitor.c:399
RESOLVE_RES_OK_EXISTING
@ RESOLVE_RES_OK_EXISTING
File exists, monitor is already attached.
Definition: monitor.c:76
Mailbox::type
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
MuttGetchTimeout
int MuttGetchTimeout
Timeout in ms for mutt_getch()
Definition: curs_lib.c:93
lib.h
lib.h
RESOLVE_RES_FAIL_NOMAILBOX
@ RESOLVE_RES_FAIL_NOMAILBOX
No Mailbox to work on.
Definition: monitor.c:72
MUTT_UNKNOWN
@ MUTT_UNKNOWN
Mailbox wasn't recognised.
Definition: mailbox.h:47
MonitorInfo::st_dev
dev_t st_dev
Definition: monitor.c:100
Context::mailbox
struct Mailbox * mailbox
Definition: context.h:50
mutt_mem_realloc
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
Monitor::next
struct Monitor * next
Definition: monitor.c:84
MUTT_MH
@ MUTT_MH
'MH' Mailbox type
Definition: mailbox.h:50
mutt_debug
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
MonitorInfo::is_dir
bool is_dir
Definition: monitor.c:98
ResolveResult
ResolveResult
Results for the Monitor functions.
Definition: monitor.c:70
Mailbox::realpath
char * realpath
Used for duplicate detection, context comparison, and the sidebar.
Definition: mailbox.h:84
mutt_poll_fd_remove
static int mutt_poll_fd_remove(int fd)
Remove a file from the watch list.
Definition: monitor.c:138
mutt_buffer_string
static const char * mutt_buffer_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:77
RESOLVE_RES_FAIL_NOTYPE
@ RESOLVE_RES_FAIL_NOTYPE
Can't identify Mailbox type.
Definition: monitor.c:73
monitor_info_init
static void monitor_info_init(struct MonitorInfo *info)
Set up a file monitor.
Definition: monitor.c:227
Monitor::mh_backup_path
char * mh_backup_path
Definition: monitor.c:85
mutt_poll_fd_add
static void mutt_poll_fd_add(int fd, short events)
Add a file to the watch list.
Definition: monitor.c:111
INOTIFY_MASK_DIR
#define INOTIFY_MASK_DIR
Definition: monitor.c:62
MonitorInfo::path_buf
struct Buffer path_buf
access via path only (maybe not initialized)
Definition: monitor.c:103
monitor_resolve
static enum ResolveResult monitor_resolve(struct MonitorInfo *info, struct Mailbox *m)
Get the monitor for a mailbox.
Definition: monitor.c:331
monitor_handle_ignore
static int monitor_handle_ignore(int desc)
Listen for when a backup file is closed.
Definition: monitor.c:273
MonitorInfo::type
enum MailboxType type
Definition: monitor.c:97
Monitor
A watch on a file.
Definition: monitor.c:82
Monitor
static struct Monitor * Monitor
Definition: monitor.c:55
MailboxType
MailboxType
Supported mailbox formats.
Definition: mailbox.h:43
context.h
MUTT_MAILDIR
@ MUTT_MAILDIR
'Maildir' Mailbox type
Definition: mailbox.h:51
monitor_check_free
static void monitor_check_free(void)
Close down file monitoring.
Definition: monitor.c:190
MonitorContextDescriptor
static int MonitorContextDescriptor
Definition: monitor.c:60
lib.h
mutt_buffer_printf
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
MonitorContextChanged
bool MonitorContextChanged
true after the current mailbox has changed
Definition: monitor.c:52
PollFdsCount
static size_t PollFdsCount
Definition: monitor.c:56
monitor_new
static struct Monitor * monitor_new(struct MonitorInfo *info, int descriptor)
Create a new file monitor.
Definition: monitor.c:207
EVENT_BUFLEN
#define EVENT_BUFLEN
Definition: monitor.c:65
LL_DEBUG2
@ LL_DEBUG2
Log at debug level 2.
Definition: logging.h:41
mailbox_find
struct Mailbox * mailbox_find(const char *path)
Find the mailbox with a given path.
Definition: mailbox.c:103
RESOLVE_RES_FAIL_STAT
@ RESOLVE_RES_FAIL_STAT
Can't stat() the Mailbox file.
Definition: monitor.c:74