/*
 *  innduct
 *  tailing reliable realtime streaming feeder for inn
 *
 *  Copyright Ian Jackson <ijackson@chiark.greenend.org.uk>
 *  and contributors; see LICENCE.txt.
 *  SPDX-License-Identifier: GPL-3.0-or-later
 */

#ifndef INNDUCT_H
#define INNDUCT_H

#define _GNU_SOURCE 1

#include "config.h"
#include "storage.h"
#include "nntp.h"
#include "libinn.h"
#include "inndcomm.h"

#include "inn/list.h"
#include "inn/innconf.h"
#include "inn/messages.h"

#include <sys/uio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <syslog.h>
#include <fcntl.h>
#include <stdarg.h>
#include <assert.h>
#include <stdlib.h>
#include <stddef.h>
#include <glob.h>
#include <time.h>
#include <math.h>
#include <ctype.h>
#include <limits.h>

#include <oop.h>
#include <oop-read.h>

/*----- general definitions, probably best not changed -----*/

#define CONNCHILD_ESTATUS_STREAM   24
#define CONNCHILD_ESTATUS_NOSTREAM 25

#define INNDCOMMCHILD_ESTATUS_FAIL     26
#define INNDCOMMCHILD_ESTATUS_NONESUCH 27

#define MAX_LINE_FEEDFILE (NNTP_MAXLEN_MSGID + sizeof(TOKEN)*2 + 10)
#define MAX_CLI_COMMAND 1000

#define VA                va_list al;  va_start(al,fmt)
#define PRINTF(f,a)       __attribute__((__format__(printf,f,a)))
#define NORET_PRINTF(f,a) __attribute__((__noreturn__,__format__(printf,f,a)))
#define NORET             __attribute__((__noreturn__))

#define NEW(ptr)              ((ptr)= zxmalloc(sizeof(*(ptr))))
#define NEW_DECL(type,ptr) type ptr = zxmalloc(sizeof(*(ptr)))

#define DUMPV(fmt,pfx,v) fprintf(f, " " #v "=" fmt, pfx v);

#define FOR_LIST_NODE(nodevar, list) \
  for ((nodevar)=LIST_HEAD(list); (nodevar); (nodevar)=LIST_NEXT((nodevar)))

#define FOR_CONN(conn) FOR_LIST_NODE(conn, conns)

/*----- doubly linked lists -----*/

#define ISNODE(T)   struct node list_node
#define DEFLIST(T)				\
   typedef struct {				\
     union { struct list li; T *for_type; } u;	\
     int count;					\
   } T##List

#define NODE(n) (assert((void*)&(n)->list_node == (n)), &(n)->list_node)

#define LIST_CHECKCANHAVENODE(l,n) \
  ((void)((n) == ((l).u.for_type))) /* just for the type check */

#define LIST_ADDSOMEHOW(l,n,list_addsomehow)	\
 ( LIST_CHECKCANHAVENODE(l,n),			\
   list_addsomehow(&(l).u.li, NODE((n))),	\
   (void)(l).count++				\
   )

#define LIST_REMSOMEHOW(l,list_remsomehow)	\
 ( (typeof((l).u.for_type))			\
   ( (l).count					\
     ? ( (l).count--,				\
	 list_remsomehow(&(l).u.li) )		\
     : 0					\
     )						\
   )


#define LIST_ADDHEAD(l,n) LIST_ADDSOMEHOW((l),(n),list_addhead)
#define LIST_ADDTAIL(l,n) LIST_ADDSOMEHOW((l),(n),list_addtail)
#define LIST_REMHEAD(l) LIST_REMSOMEHOW((l),list_remhead)
#define LIST_REMTAIL(l) LIST_REMSOMEHOW((l),list_remtail)

#define LIST_INIT(l) ((l).count=0, list_new(&(l).u.li))
#define LIST_HEAD(l) ((typeof((l).u.for_type))(list_head((struct list*)&(l))))
#define LIST_NEXT(n) ((typeof(n))list_succ(NODE((n))))
#define LIST_BACK(n) ((typeof(n))list_pred(NODE((n))))

#define LIST_REMOVE(l,n)			\
 ( LIST_CHECKCANHAVENODE(l,n),			\
   list_remove(NODE((n))),			\
   (void)(l).count--				\
   )

#define LIST_INSERT(l,n,pred)					\
 ( LIST_CHECKCANHAVENODE(l,n),					\
   LIST_CHECKCANHAVENODE(l,pred),				\
   list_insert((struct list*)&(l), NODE((n)), NODE((pred))),	\
   (void)(l).count++						\
   )

/*----- type predeclarations -----*/

typedef struct Conn Conn;
typedef struct Article Article;
typedef struct InputFile InputFile;
typedef struct XmitDetails XmitDetails;
typedef struct Filemon_Perfile Filemon_Perfile;
typedef enum StateMachineState StateMachineState;
typedef struct CliCommand CliCommand;

DEFLIST(Conn);
DEFLIST(Article);


/*----- configuration options -----*/

extern const char *sitename, *remote_host;
extern const char *feedfile, *path_run, *path_cli, *path_cli_dir;
extern int quiet_multiple, interactive, try_filemon, try_stream, port;
extern const char *inndconffile;

extern int max_connections, max_queue_per_conn, target_max_feedfile_size;
extern int period_seconds, filepoll_seconds, max_queue_per_ipf;
extern int connection_setup_timeout, inndcomm_flush_timeout;

extern double nocheck_thresh;
extern double nocheck_decay;

/* all these are initialised to seconds, and converted to periods in main */
extern int reconnect_delay_periods;
extern int flushfail_retry_periods;
extern int backlog_retry_minperiods;
extern int backlog_spontrescan_periods;
extern int spontaneous_flush_periods;
extern int max_separated_periods;
extern int need_activity_periods;
extern int stats_log_periods;
extern int lowvol_thresh;
extern int lowvol_periods;

extern double max_bad_data_ratio;
extern int max_bad_data_initial;


/*----- article states, and statistics -----*/

typedef enum {      /* in queue                 in conn->sent             */
  art_Unchecked,    /*   not checked, not sent    checking                */
  art_Wanted,       /*   checked, wanted          sent body as requested  */
  art_Unsolicited,  /*   -                        sent body without check */
  art_MaxState,
} ArtState;
extern const char *const artstate_names[]; /* xmit.c */

#define RESULT_COUNTS(RCS,RCN)			\
  RCS(sent)					\
  RCS(accepted)					\
  RCN(unwanted)					\
  RCN(rejected)					\
  RCN(deferred)					\
  RCN(missing)					\
  RCN(connretry)

#define RCI_TRIPLE_FMT_BASE "%d (id=%d,bod=%d,nc=%d)"
#define RCI_TRIPLE_VALS_BASE(counts,x)		\
       counts[art_Unchecked] x			\
       + counts[art_Wanted] x			\
       + counts[art_Unsolicited] x,		\
       counts[art_Unchecked] x			\
       , counts[art_Wanted] x			\
       , counts[art_Unsolicited] x

typedef enum {
#define RC_INDEX(x) RC_##x,
  RESULT_COUNTS(RC_INDEX, RC_INDEX)
  RCI_max
} ResultCountIndex;

typedef enum {
  read_ok, read_blank, read_err, nooffer_missing,
  ECI_max
} EventCountIndex;

/*----- transmission buffers -----*/

#define CONNIOVS 128

typedef enum {
  xk_Const, xk_Artdata
} XmitKind;

struct XmitDetails {
  XmitKind kind;
  union {
    ARTHANDLE *sm_art;
  } info;
};


/*----- core operational data structure types -----*/

typedef struct {
  int results[art_MaxState][RCI_max];
  int events[ECI_max];
} Counts;

struct InputFile {
  /* This is also an instance of struct oop_readable */
  struct oop_readable readable; /* first */
  oop_readable_call *readable_callback;
  void *readable_callback_user;

  int fd;
  Filemon_Perfile *filemon;

  oop_read *rd; /* non-0: reading; 0: constructing, or had EOF */
  off_t offset;
  int skippinglong, paused, fake_readable;

  ArticleList queue;
  long inprogress; /* includes queue.count and also articles in conns */
  long autodefer; /* -1 means not doing autodefer */

  Counts counts;
  char path[];
};

struct Article {
  ISNODE(Article);
  ArtState state;
  int midlen, missing;
  InputFile *ipf;
  TOKEN token;
  off_t offset;
  int blanklen;
  char messageid[1];
};

struct Conn {
  ISNODE(Conn);
  int fd; /* may be 0, meaning closed (during construction/destruction) */
  oop_read *rd; /* likewise */
  int oopwriting; /* since on_fd is not idempotent */
  int max_queue, stream;
  const char *quitting;
  int since_activity; /* periods */
  ArticleList waiting; /* not yet told peer */
  ArticleList priority; /* peer says send it now */
  ArticleList sent; /* offered/transmitted - in xmit or waiting reply */
  struct iovec xmit[CONNIOVS];
  XmitDetails xmitd[CONNIOVS];
  int xmitu;
};

#define SMS_LIST(X)				\
  X(NORMAL)					\
  X(FLUSHING)					\
  X(FLUSHFAILED)				\
  X(SEPARATED)					\
  X(DROPPING)					\
  X(DROPPED)

enum StateMachineState {
#define SMS_DEF_ENUM(s) sm_##s,
  SMS_LIST(SMS_DEF_ENUM)
};

extern const char *sms_names[];

/*========== function declarations ==========*/

/*----- help.c -----*/

void syscrash(const char *fmt, ...) NORET_PRINTF(1,2);
void crash(const char *fmt, ...) NORET_PRINTF(1,2);
void info(const char *fmt, ...) PRINTF(1,2);
void dbg(const char *fmt, ...) PRINTF(1,2);

void logv(int sysloglevel, const char *pfx, int errnoval,
	  const char *fmt, va_list al) PRINTF(4,0);

char *mvasprintf(const char *fmt, va_list al) PRINTF(1,0);
char *masprintf(const char *fmt, ...) PRINTF(1,2);

int close_perhaps(int *fd);
void xclose(int fd, const char *what, const char *what2);
void xclose_perhaps(int *fd, const char *what, const char *what2);
pid_t xfork(const char *what); /* also runs postfork in child */
pid_t xfork_bare(const char *what);

void on_fd_read_except(int fd, oop_call_fd callback);
void cancel_fd_read_except(int fd);

void report_child_status(const char *what, int status);
int xwaitpid(pid_t *pid, const char *what);

void *zxmalloc(size_t sz);
void xunlink(const char *path, const char *what);
time_t xtime(void);
void xsigaction(int signo, const struct sigaction *sa);
void xsigsetdefault(int signo);
void raise_default(int signo) NORET;

void xgettimeofday(struct timeval *tv_r);
void xsetnonblock(int fd, int nonb);

void check_isreg(const struct stat *stab, const char *path,
			const char *what);
void xfstat(int fd, struct stat *stab_r, const char *what);
void xfstat_isreg(int fd, struct stat *stab_r,
		  const char *path, const char *what);
void xlstat_isreg(const char *path, struct stat *stab,
		  int *enoent_r /* 0 means ENOENT is fatal */,
		  const char *what);
int samefile(const struct stat *a, const struct stat *b);

char *sanitise(const char *input, int len);

static inline int isewouldblock(int errnoval) {
  return errnoval==EWOULDBLOCK || errnoval==EAGAIN;
}

#define INNLOGSETS(INNLOGSET)			\
  INNLOGSET(die,      "fatal",    LOG_ERR)	\
  INNLOGSET(warn,     "warning",  LOG_WARNING)	\
  INNLOGSET(notice,   "notice",   LOG_NOTICE)	\
  INNLOGSET(debug,    "debug",    LOG_DEBUG)
#define INNLOGSET_DECLARE(fn, pfx, sysloglevel)				\
  void duct_log_##fn(int l, const char *fmt, va_list al, int errval)	\
    PRINTF(2,0);
INNLOGSETS(INNLOGSET_DECLARE)

/*----- duct.c -----*/

void postfork(void);
void period(void);

/*----- cli.c -----*/

void cli_init(void);
void cli_stdio(void);

/*----- conn.c -----*/

void conn_closefd(Conn *conn, const char *msgprefix);
void check_idle_conns(void);
int conn_busy(Conn *conn);
void conn_dispose(Conn *conn);

void vconnfail(Conn *conn, const char *fmt, va_list al) PRINTF(2,0);
void connfail(Conn *conn, const char *fmt, ...)         PRINTF(2,3);

void notice_conns_more(const char *new_kind);
void notice_conns_fewer(void);
void notice_conns_stats(void);

int allow_connect_start(void);
void connect_start(void);

/*----- defer.c -----*/

void poll_backlog_file(void);
void search_backlog_file(void);
void open_defer(void);
void close_defer(void);

/*----- filemon.c -----*/

int filemon_method_init(void);
void filemon_method_dump_info(FILE *f);

void filemon_start(InputFile *ipf);
void filemon_stop(InputFile *ipf);

/*----- infile.c -----*/

void filepoll(void);

void inputfile_reading_start(InputFile *ipf);
void inputfile_reading_stop(InputFile *ipf);
void inputfile_reading_pause(InputFile *ipf);
void inputfile_reading_resume(InputFile *ipf);
  /* pause and resume are idempotent, and no-op if not done _reading_start */

InputFile *open_input_file(const char *path);
void close_input_file(InputFile *ipf); /* does not free */
char *dbg_report_ipf(InputFile *ipf);

void tailing_make_readable(InputFile *ipf);

/*----- recv.c -----*/

extern const oop_rd_style peer_rd_style;
oop_rd_call peer_rd_ok, peer_rd_err;

void article_done(Article *art, int whichcount);

/*----- statemc.c -----*/

void statemc_check_flushing_done(void);
void statemc_check_backlog_done(void);

extern sig_atomic_t terminate_sig_flag;
void statemc_period_poll(void);
void statemc_lock(void);
void init_signals(void);
void statemc_init(void);
void showstats(void);

#define SMS(newstate, periods, why) \
   (statemc_setstate(sm_##newstate,(periods),#newstate,(why)))
void statemc_setstate(StateMachineState newsms, int periods,
			     const char *forlog, const char *why);

void statemc_start_flush(const char *why); /* Normal => Flushing */
void spawn_inndcomm_flush(const char *why); /* Moved => Flushing */
int trigger_flush_ok(const char *why /* 0 means timeout */);
                                  /* => Flushing,FLUSHING, ret 1; or ret 0 */

void preterminate(void);

/*----- xmit.c -----*/

void inputfile_queue_check_expired(InputFile *ipf);
void check_assign_articles(void);
void queue_check_input_done(void);
void check_reading_pause_resume(InputFile *ipf);

void conn_maybe_write(Conn *conn);
void conn_make_some_xmits(Conn *conn);
void *conn_write_some_xmits(Conn *conn);

void xmit_free(XmitDetails *d);

int article_check_expired(Article *art /* must be queued, not conn */);
void article_autodefer(InputFile *ipf, Article *art);
void article_defer(Article *art /* not on a queue */, int whichcount);
void autodefer_input_file(InputFile *ipf);

/*----- external linkage for debug/dump only -----*/

extern pid_t connecting_child;
extern int connecting_fdpass_sock;
extern pid_t inndcomm_child;

/*========== general operational variables ==========*/

/* duct.c */
extern oop_source *loop;
extern ConnList conns;
extern char *path_lock, *path_flushing, *path_defer, *path_dump;
extern char *globpat_backlog;
extern pid_t self_pid;
extern int *lowvol_perperiod;
extern int lowvol_circptr;
extern int lowvol_total; /* does not include current period */
extern int until_stats_log;

/* statemc.c */
extern StateMachineState sms;
extern int until_flush;
extern InputFile *main_input_file, *flushing_input_file, *backlog_input_file;
extern Counts backlog_counts;
extern int backlog_counts_report;
extern FILE *defer;
extern int until_connect, until_backlog_nextscan;
extern double accept_proportion;
extern int nocheck, nocheck_reported, in_child;

/* help.c */
extern int simulate_flush;
extern int logv_use_syslog;
extern const char *logv_prefix;


#endif /*INNDUCT_H*/
