/*
 *  innduct
 *  tailing reliable realtime streaming feeder for inn
 *  filemon.c - file monitoring (inotify, kqueue, poll, etc.)
 *
 *  Copyright Ian Jackson <ijackson@chiark.greenend.org.uk>
 *  and contributors; see LICENCE.txt.
 *  SPDX-License-Identifier: GPL-3.0-or-later
 */

#include "innduct.h"

/*---------- filemon implemented with inotify ----------*/

#if defined(HAVE_SYS_INOTIFY_H) && !defined(HAVE_FILEMON)
#define HAVE_FILEMON

#include <sys/inotify.h>

DEFLIST(Filemon_Perfile);

struct Filemon_Perfile {
  ISNODE(Filemon_Perfile);
  InputFile *ipf;
  int wd;
};

static int filemon_inotify_fd;
static Filemon_PerfileList filemon_inotify_watches;

static void filemon_method_startfile(InputFile *ipf, Filemon_Perfile *pf) {
  pf->ipf= ipf;

  pf->wd= inotify_add_watch(filemon_inotify_fd, ipf->path, IN_MODIFY);
  if (pf->wd < 0) sysdie("filemon inotify: inotify_add_watch %s", ipf->path);

  LIST_ADDHEAD(filemon_inotify_watches, pf);
  dbg("filemon inotify: startfile %p wd=%d pf=%p", ipf, pf->wd, pf);
}

static void filemon_method_stopfile(InputFile *ipf, Filemon_Perfile *pf) {
  dbg("filemon inotify: stopfile %p wd=%d pf=%p", ipf, pf->wd, pf);
  int r= inotify_rm_watch(filemon_inotify_fd, pf->wd);
  if (r) syscrash("filemon inotify: inotify_rm_watch");
  LIST_REMOVE(filemon_inotify_watches, pf);
}

static void *filemon_inotify_readable(oop_source *lp, int fd,
				      oop_event e, void *u) {
  struct inotify_event iev;
  InputFile *ipf;
  for (;;) {
    int r= read(filemon_inotify_fd, &iev, sizeof(iev));
    if (r==-1) {
      if (isewouldblock(errno)) break;
      syscrash("filemon inotify: read from inotify master");
    } else if (r!=sizeof(iev)) {
      crash("filemon inotify: read %d bytes when wanted struct of %d",
	    r, (int)sizeof(iev));
    }
    Filemon_Perfile *pf;
    FOR_LIST_NODE(pf, filemon_inotify_watches)
      if (pf->wd == iev.wd) goto found;
    /* Linux seems to remember events and can produce them even after
     * you've removed the watch.  This means that we can't spot bugs
     * where we lose track of our watches and have to just regard
     * unexpected random watch events as normal.  It's not a
     * correctness problem as the watch is just a prod to read a file,
     * which is harmless if it does not need to be read. */
    dbg("filemon inotify: read event with unknown wd=%d", iev.wd);
    continue;
    
  found:
    ipf= pf->ipf;
    /*dbg("filemon inotify readable read %p wd=%d", ipf, iev.wd);*/
    tailing_make_readable(ipf);
  }
  return OOP_CONTINUE;
}

int filemon_method_init(void) {
  LIST_INIT(filemon_inotify_watches);
  filemon_inotify_fd= inotify_init();
  if (filemon_inotify_fd<0) {
    syswarn("filemon inotify: inotify_init failed");
    return 0;
  }
  xsetnonblock(filemon_inotify_fd, 1);
  loop->on_fd(loop, filemon_inotify_fd, OOP_READ, filemon_inotify_readable, 0);

  dbg("filemon inotify: init filemon_inotify_fd=%d", filemon_inotify_fd);
  return 1;
}

void filemon_method_dump_info(FILE *f) {
  fprintf(f,"inotify");
  DUMPV("%d",,filemon_inotify_fd);
  DUMPV("%d",filemon_inotify_watches.,count);
  fprintf(f,"\n");
  Filemon_Perfile *pf;
  FOR_LIST_NODE(pf, filemon_inotify_watches)
    fprintf(f," watching %p wd=%d pf=%p\n", pf->ipf, pf->wd, pf);
}

#endif /* HAVE_INOTIFY && !HAVE_FILEMON */

/*---------- filemon dummy implementation ----------*/

#if !defined(HAVE_FILEMON)

struct Filemon_Perfile { int dummy; };

int filemon_method_init(void) {
  warn("filemon dummy: no filemon method compiled in");
  return 0;
}
static void filemon_method_startfile(InputFile *ipf, Filemon_Perfile *pf) { }
static void filemon_method_stopfile(InputFile *ipf, Filemon_Perfile *pf) { }
void filemon_method_dump_info(FILE *f) { fprintf(f,"dummy\n"); }

#endif /* !HAVE_FILEMON */

/*---------- filemon generic interface ----------*/

void filemon_start(InputFile *ipf) {
  assert(!ipf->filemon);

  NEW(ipf->filemon);
  filemon_method_startfile(ipf, ipf->filemon);
}

void filemon_stop(InputFile *ipf) {
  if (!ipf->filemon) return;
  filemon_method_stopfile(ipf, ipf->filemon);
  free(ipf->filemon);
  ipf->filemon= 0;
}
