mutt stable branch with some hacks
at jcs 497 lines 12 kB view raw
1/* * Copyright (C) 2018 Gero Treuner <gero@70t.de> 2 * 3 * This program is free software; you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation; either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program; if not, write to the Free Software 15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 */ 17 18#if HAVE_CONFIG_H 19# include "config.h" 20#endif 21 22#if HAVE_SYS_INOTIFY_H 23# include <sys/types.h> 24# include <sys/inotify.h> 25# include <unistd.h> 26# include <poll.h> 27#endif 28#ifndef HAVE_INOTIFY_INIT1 29# include <fcntl.h> 30#endif 31 32#include "mutt.h" 33#include "buffy.h" 34#include "monitor.h" 35#include "mx.h" 36#include "mutt_curses.h" 37 38#include <errno.h> 39#include <sys/stat.h> 40 41typedef struct monitor_t 42{ 43 struct monitor_t *next; 44 char *mh_backup_path; 45 dev_t st_dev; 46 ino_t st_ino; 47 short magic; 48 int descr; 49} 50MONITOR; 51 52static int INotifyFd = -1; 53static MONITOR *Monitor = NULL; 54static size_t PollFdsCount = 0; 55static size_t PollFdsLen = 0; 56static struct pollfd *PollFds; 57 58static int MonitorContextDescriptor = -1; 59 60typedef struct monitorinfo_t 61{ 62 short magic; 63 short isdir; 64 const char *path; 65 dev_t st_dev; 66 ino_t st_ino; 67 MONITOR *monitor; 68 BUFFER *_pathbuf; /* access via path only (maybe not initialized) */ 69} 70MONITORINFO; 71 72#define INOTIFY_MASK_DIR (IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE | IN_ISDIR) 73#define INOTIFY_MASK_FILE IN_CLOSE_WRITE 74 75static void mutt_poll_fd_add(int fd, short events) 76{ 77 int i = 0; 78 for (i = 0; i < PollFdsCount && PollFds[i].fd != fd; ++i); 79 80 if (i == PollFdsCount) 81 { 82 if (PollFdsCount == PollFdsLen) 83 { 84 PollFdsLen += 2; 85 safe_realloc (&PollFds, PollFdsLen * sizeof(struct pollfd)); 86 } 87 ++PollFdsCount; 88 PollFds[i].fd = fd; 89 PollFds[i].events = events; 90 } 91 else 92 PollFds[i].events |= events; 93} 94 95static int mutt_poll_fd_remove(int fd) 96{ 97 int i = 0, d; 98 for (i = 0; i < PollFdsCount && PollFds[i].fd != fd; ++i); 99 if (i == PollFdsCount) 100 return -1; 101 d = PollFdsCount - i - 1; 102 if (d) 103 memmove (&PollFds[i], &PollFds[i + 1], d * sizeof(struct pollfd)); 104 --PollFdsCount; 105 return 0; 106} 107 108static int monitor_init () 109{ 110 if (INotifyFd == -1) 111 { 112#if HAVE_INOTIFY_INIT1 113 INotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 114 if (INotifyFd == -1) 115 { 116 dprint (2, (debugfile, "monitor: inotify_init1 failed, errno=%d %s\n", errno, strerror(errno))); 117 return -1; 118 } 119#else 120 INotifyFd = inotify_init(); 121 if (INotifyFd == -1) 122 { 123 dprint (2, (debugfile, "monitor: inotify_init failed, errno=%d %s\n", errno, strerror(errno))); 124 return -1; 125 } 126 fcntl(INotifyFd, F_SETFL, O_NONBLOCK); 127 fcntl(INotifyFd, F_SETFD, FD_CLOEXEC); 128#endif 129 mutt_poll_fd_add(0, POLLIN); 130 mutt_poll_fd_add(INotifyFd, POLLIN); 131 } 132 return 0; 133} 134 135static void monitor_check_free () 136{ 137 if (!Monitor && INotifyFd != -1) 138 { 139 mutt_poll_fd_remove(INotifyFd); 140 close (INotifyFd); 141 INotifyFd = -1; 142 MonitorFilesChanged = 0; 143 } 144} 145 146static MONITOR *monitor_create (MONITORINFO *info, int descriptor) 147{ 148 MONITOR *monitor = (MONITOR *) safe_calloc (1, sizeof (MONITOR)); 149 monitor->magic = info->magic; 150 monitor->st_dev = info->st_dev; 151 monitor->st_ino = info->st_ino; 152 monitor->descr = descriptor; 153 monitor->next = Monitor; 154 if (info->magic == MUTT_MH) 155 monitor->mh_backup_path = safe_strdup(info->path); 156 157 Monitor = monitor; 158 159 return monitor; 160} 161 162static void monitor_info_init (MONITORINFO *info) 163{ 164 memset (info, 0, sizeof (MONITORINFO)); 165} 166 167static void monitor_info_free (MONITORINFO *info) 168{ 169 mutt_buffer_free (&info->_pathbuf); 170} 171 172static void monitor_delete (MONITOR *monitor) 173{ 174 MONITOR **ptr = &Monitor; 175 176 if (!monitor) 177 return; 178 179 FOREVER 180 { 181 if (!*ptr) 182 return; 183 if (*ptr == monitor) 184 break; 185 ptr = &(*ptr)->next; 186 } 187 188 FREE (&monitor->mh_backup_path); /* __FREE_CHECKED__ */ 189 monitor = monitor->next; 190 FREE (ptr); /* __FREE_CHECKED__ */ 191 *ptr = monitor; 192} 193 194static int monitor_handle_ignore (int descr) 195{ 196 int new_descr = -1; 197 MONITOR *iter = Monitor; 198 struct stat sb; 199 200 while (iter && iter->descr != descr) 201 iter = iter->next; 202 203 if (iter) 204 { 205 if (iter->magic == MUTT_MH && stat (iter->mh_backup_path, &sb) == 0) 206 { 207 if ((new_descr = inotify_add_watch (INotifyFd, iter->mh_backup_path, INOTIFY_MASK_FILE)) == -1) 208 dprint (2, (debugfile, "monitor: inotify_add_watch failed for '%s', errno=%d %s\n", iter->mh_backup_path, errno, strerror(errno))); 209 else 210 { 211 dprint (3, (debugfile, "monitor: inotify_add_watch descriptor=%d for '%s'\n", descr, iter->mh_backup_path)); 212 iter->st_dev = sb.st_dev; 213 iter->st_ino = sb.st_ino; 214 iter->descr = new_descr; 215 } 216 } 217 else 218 { 219 dprint (3, (debugfile, "monitor: cleanup watch (implicitly removed) - descriptor=%d\n", descr)); 220 } 221 222 if (MonitorContextDescriptor == descr) 223 MonitorContextDescriptor = new_descr; 224 225 if (new_descr == -1) 226 { 227 monitor_delete (iter); 228 monitor_check_free (); 229 } 230 } 231 232 return new_descr; 233} 234 235#define EVENT_BUFLEN MAX(4096, sizeof(struct inotify_event) + NAME_MAX + 1) 236 237/* mutt_monitor_poll: Waits for I/O ready file descriptors or signals. 238 * 239 * return values: 240 * -3 unknown/unexpected events: poll timeout / fds not handled by us 241 * -2 monitor detected changes, no STDIN input 242 * -1 error (see errno) 243 * 0 (1) input ready from STDIN, or (2) monitoring inactive -> no poll() 244 * MonitorFilesChanged also reflects changes to monitored files. 245 * 246 * Only STDIN and INotify file handles currently expected/supported. 247 * More would ask for common infrastructur (sockets?). 248 */ 249int mutt_monitor_poll (void) 250{ 251 int rc = 0, fds, i, inputReady; 252 char buf[EVENT_BUFLEN] 253 __attribute__ ((aligned(__alignof__(struct inotify_event)))); 254 255 MonitorFilesChanged = 0; 256 257 if (INotifyFd != -1) 258 { 259 fds = poll (PollFds, PollFdsLen, MuttGetchTimeout); 260 261 if (fds == -1) 262 { 263 rc = -1; 264 if (errno != EINTR) 265 { 266 dprint (2, (debugfile, "monitor: poll() failed, errno=%d %s\n", errno, strerror(errno))); 267 } 268 } 269 else 270 { 271 inputReady = 0; 272 for (i = 0; fds && i < PollFdsCount; ++i) 273 { 274 if (PollFds[i].revents) 275 { 276 --fds; 277 if (PollFds[i].fd == 0) 278 { 279 inputReady = 1; 280 } 281 else if (PollFds[i].fd == INotifyFd) 282 { 283 MonitorFilesChanged = 1; 284 dprint (3, (debugfile, "monitor: file change(s) detected\n")); 285 int len; 286 char *ptr = buf; 287 const struct inotify_event *event; 288 289 FOREVER 290 { 291 len = read (INotifyFd, buf, sizeof(buf)); 292 if (len == -1) 293 { 294 if (errno != EAGAIN) 295 dprint (2, (debugfile, "monitor: read inotify events failed, errno=%d %s\n", 296 errno, strerror(errno))); 297 break; 298 } 299 300 while (ptr < buf + len) 301 { 302 event = (const struct inotify_event *) ptr; 303 dprint (5, (debugfile, "monitor: + detail: descriptor=%d mask=0x%x\n", 304 event->wd, event->mask)); 305 if (event->mask & IN_IGNORED) 306 monitor_handle_ignore (event->wd); 307 else if (event->wd == MonitorContextDescriptor) 308 MonitorContextChanged = 1; 309 ptr += sizeof(struct inotify_event) + event->len; 310 } 311 } 312 } 313 } 314 } 315 if (!inputReady) 316 rc = MonitorFilesChanged ? -2 : -3; 317 } 318 } 319 320 return rc; 321} 322 323#define RESOLVERES_OK_NOTEXISTING 0 324#define RESOLVERES_OK_EXISTING 1 325#define RESOLVERES_FAIL_NOMAILBOX -3 326#define RESOLVERES_FAIL_NOMAGIC -2 327#define RESOLVERES_FAIL_STAT -1 328 329/* monitor_resolve: resolve monitor entry match by BUFFY, or - if NULL - by Context. 330 * 331 * return values: 332 * >=0 mailbox is valid and locally accessible: 333 * 0: no monitor / 1: preexisting monitor 334 * -3 no mailbox (MONITORINFO: no fields set) 335 * -2 magic not set 336 * -1 stat() failed (see errno; MONITORINFO fields: magic, isdir, path) 337 */ 338static int monitor_resolve (MONITORINFO *info, BUFFY *buffy) 339{ 340 MONITOR *iter; 341 char *fmt = NULL; 342 struct stat sb; 343 344 if (buffy) 345 { 346 info->magic = buffy->magic; 347 info->path = buffy->realpath; 348 } 349 else if (Context) 350 { 351 info->magic = Context->magic; 352 info->path = Context->realpath; 353 } 354 else 355 { 356 return RESOLVERES_FAIL_NOMAILBOX; 357 } 358 359 if (!info->magic) 360 { 361 return RESOLVERES_FAIL_NOMAGIC; 362 } 363 else if (info->magic == MUTT_MAILDIR) 364 { 365 info->isdir = 1; 366 fmt = "%s/new"; 367 } 368 else 369 { 370 info->isdir = 0; 371 if (info->magic == MUTT_MH) 372 fmt = "%s/.mh_sequences"; 373 } 374 375 if (fmt) 376 { 377 if (!info->_pathbuf) 378 info->_pathbuf = mutt_buffer_new (); 379 mutt_buffer_printf (info->_pathbuf, fmt, info->path); 380 info->path = mutt_b2s (info->_pathbuf); 381 } 382 if (stat (info->path, &sb) != 0) 383 return RESOLVERES_FAIL_STAT; 384 385 iter = Monitor; 386 while (iter && (iter->st_ino != sb.st_ino || iter->st_dev != sb.st_dev)) 387 iter = iter->next; 388 389 info->st_dev = sb.st_dev; 390 info->st_ino = sb.st_ino; 391 info->monitor = iter; 392 393 return iter ? RESOLVERES_OK_EXISTING : RESOLVERES_OK_NOTEXISTING; 394} 395 396/* mutt_monitor_add: add file monitor from BUFFY, or - if NULL - from Context. 397 * 398 * return values: 399 * 0 success: new or already existing monitor 400 * -1 failed: no mailbox, inaccessible file, create monitor/watcher failed 401 */ 402int mutt_monitor_add (BUFFY *buffy) 403{ 404 MONITORINFO info; 405 uint32_t mask; 406 int descr, rc = 0; 407 408 monitor_info_init (&info); 409 410 descr = monitor_resolve (&info, buffy); 411 if (descr != RESOLVERES_OK_NOTEXISTING) 412 { 413 if (!buffy && (descr == RESOLVERES_OK_EXISTING)) 414 MonitorContextDescriptor = info.monitor->descr; 415 rc = descr == RESOLVERES_OK_EXISTING ? 0 : -1; 416 goto cleanup; 417 } 418 419 mask = info.isdir ? INOTIFY_MASK_DIR : INOTIFY_MASK_FILE; 420 if ((INotifyFd == -1 && monitor_init () == -1) 421 || (descr = inotify_add_watch (INotifyFd, info.path, mask)) == -1) 422 { 423 dprint (2, (debugfile, "monitor: inotify_add_watch failed for '%s', errno=%d %s\n", info.path, errno, strerror(errno))); 424 rc = -1; 425 goto cleanup; 426 } 427 428 dprint (3, (debugfile, "monitor: inotify_add_watch descriptor=%d for '%s'\n", descr, info.path)); 429 if (!buffy) 430 MonitorContextDescriptor = descr; 431 432 monitor_create (&info, descr); 433 434cleanup: 435 monitor_info_free (&info); 436 return rc; 437} 438 439/* mutt_monitor_remove: remove file monitor from BUFFY, or - if NULL - from Context. 440 * 441 * return values: 442 * 0 monitor removed (not shared) 443 * 1 monitor not removed (shared) 444 * 2 no monitor 445 */ 446int mutt_monitor_remove (BUFFY *buffy) 447{ 448 MONITORINFO info, info2; 449 int rc = 0; 450 451 monitor_info_init (&info); 452 monitor_info_init (&info2); 453 454 if (!buffy) 455 { 456 MonitorContextDescriptor = -1; 457 MonitorContextChanged = 0; 458 } 459 460 if (monitor_resolve (&info, buffy) != RESOLVERES_OK_EXISTING) 461 { 462 rc = 2; 463 goto cleanup; 464 } 465 466 if (Context) 467 { 468 if (buffy) 469 { 470 if (monitor_resolve (&info2, NULL) == RESOLVERES_OK_EXISTING 471 && info.st_ino == info2.st_ino && info.st_dev == info2.st_dev) 472 { 473 rc = 1; 474 goto cleanup; 475 } 476 } 477 else 478 { 479 if (mutt_find_mailbox (Context->realpath)) 480 { 481 rc = 1; 482 goto cleanup; 483 } 484 } 485 } 486 487 inotify_rm_watch(info.monitor->descr, INotifyFd); 488 dprint (3, (debugfile, "monitor: inotify_rm_watch for '%s' descriptor=%d\n", info.path, info.monitor->descr)); 489 490 monitor_delete (info.monitor); 491 monitor_check_free (); 492 493cleanup: 494 monitor_info_free (&info); 495 monitor_info_free (&info2); 496 return rc; 497}