text: #include "pgqd.h"
  
  #include <getopt.h>
  
  #include <usual/event.h>
  #include <usual/string.h>
  #include <usual/signal.h>
  #include <usual/daemon.h>
  #include <usual/cfparser.h>
  #include <usual/time.h>
  
  static void detect_dbs(void);
  static void recheck_dbs(void);
  
  static const char usage_str[] =
  "usage: pgq-ticker [switches] config.file\n"
  "Switches:\n"
  "  -v        Increase verbosity\n"
  "  -q        No output to console\n"
  "  -d        Daemonize\n"
  "  -h        Show help\n"
  "  -V        Show version\n"
  " --ini      Show sample config file\n"
  "  -s        Stop - send SIGINT to running process\n"
  "  -k        Kill - send SIGTERM to running process\n"
  #ifdef SIGHUP
  "  -r        Reload - send SIGHUP to running process\n"
  #endif
  "";
  
  static const char *sample_ini =
  #include "pgqd.ini.h"
  ;
  
  struct Config cf;
  
  struct Stats stats;
  
  static struct PgSocket *db_template;
  
  static STATLIST(database_list);
  
  static int got_sigint;
  
  #define CF_REL_BASE struct Config
  static const struct CfKey conf_params[] = {
  	CF_ABS("logfile", CF_FILE, cf_logfile, 0, NULL),
  	CF_REL("pidfile", CF_FILE, pidfile, 0, NULL),
  	CF_REL("initial_database", CF_STR, initial_database, 0, "template1"),
  	CF_REL("base_connstr", CF_STR, base_connstr, 0, ""),
  	CF_REL("database_list", CF_STR, database_list, 0, NULL),
  	CF_ABS("syslog", CF_INT, cf_syslog, 0, "1"),
  	CF_ABS("syslog_ident", CF_STR, cf_syslog_ident, 0, "pgqd"),
  	CF_ABS("syslog_facility", CF_STR, cf_syslog_facility, 0, "daemon"),
  	CF_REL("check_period", CF_TIME_DOUBLE, check_period, 0, "60"),
  	CF_REL("maint_period", CF_TIME_DOUBLE, maint_period, 0, "120"),
  	CF_REL("retry_period", CF_TIME_DOUBLE, retry_period, 0, "30"),
  	CF_REL("ticker_period", CF_TIME_DOUBLE, ticker_period, 0, "1"),
  	CF_REL("stats_period", CF_TIME_DOUBLE, stats_period, 0, "30"),
  	CF_REL("connection_lifetime", CF_TIME_DOUBLE, connection_lifetime, 0, "3600"),
  	{ NULL },
  };
  
  static const struct CfSect conf_sects[] = {
  	{ "pgqd", conf_params },
  	{ NULL }
  };
  
  static struct CfContext conf_info = {
  	.sect_list = conf_sects,
  	.base = &cf,
  };
  
  static void load_config(void)
  {
  	bool ok = cf_load_file(&conf_info, cf.config_file);
  	if (!ok)
  		fatal("failed to read config");
  	reset_logging();
  }
  
  static void handle_sigterm(int sock, short flags, void *arg)
  {
  	log_info("Got SIGTERM, fast exit");
  	/* pidfile cleanup happens via atexit() */
  	exit(1);
  }
  
  static void handle_sigint(int sock, short flags, void *arg)
  {
  	log_info("Got SIGINT, shutting down");
  	/* notify main loop to exit */
  	got_sigint = 1;
  }
  
  static void handle_sighup(int sock, short flags, void *arg)
  {
  	log_info("Got SIGHUP, re-reading config");
  	load_config();
  	recheck_dbs();
  }
  
  static void signal_setup(void)
  {
  	static struct event ev_sighup;
  	static struct event ev_sigterm;
  	static struct event ev_sigint;
  
  	int err;
  
  #ifdef SIGPIPE
  	sigset_t set;
  
  	/* block SIGPIPE */
  	sigemptyset(&set);
  	sigaddset(&set, SIGPIPE);
  	err = sigprocmask(SIG_BLOCK, &set, NULL);
  	if (err < 0)
  		fatal_perror("sigprocmask");
  #endif
  
  #ifdef SIGHUP
  	/* catch signals */
  	signal_set(&ev_sighup, SIGHUP, handle_sighup, NULL);
  	err = signal_add(&ev_sighup, NULL);
  	if (err < 0)
  		fatal_perror("signal_add");
  #endif
  
  	signal_set(&ev_sigterm, SIGTERM, handle_sigterm, NULL);
  	err = signal_add(&ev_sigterm, NULL);
  	if (err < 0)
  		fatal_perror("signal_add");
  
  	signal_set(&ev_sigint, SIGINT, handle_sigint, NULL);
  	err = signal_add(&ev_sigint, NULL);
  	if (err < 0)
  		fatal_perror("signal_add");
  }
  
  const char *make_connstr(const char *dbname)
  {
  	static char buf[512];
  	snprintf(buf, sizeof(buf), "%s dbname=%s ", cf.base_connstr, dbname);
  	return buf;
  }
  
  static void launch_db(const char *dbname)
  {
  	struct PgDatabase *db;
  	struct List *elem;
  
  	/* check of already exists */
  	statlist_for_each(elem, &database_list) {
  		db = container_of(elem, struct PgDatabase, head);
  		if (strcmp(db->name, dbname) == 0) {
  			db->dropped = false;
  			return;
  		}
  	}
  
  	/* create new db entry */
  	db = calloc(1, sizeof(*db));
  	db->name = strdup(dbname);
  	list_init(&db->head);
  	statlist_init(&db->maint_op_list, "maint_op_list");
  	statlist_append(&database_list, &db->head);
  
  	/* start working on it */
  	launch_ticker(db);
  }
  
  static void drop_db(struct PgDatabase *db, bool log)
  {
  	if (log)
  		log_info("Unregister database: %s", db->name);
  	statlist_remove(&database_list, &db->head);
  	pgs_free(db->c_ticker);
  	pgs_free(db->c_maint);
  	pgs_free(db->c_retry);
  	free_maint(db);
  	free(db->name);
  	free(db);
  }
  
  static void detect_handler(struct PgSocket *sk, void *arg, enum PgEvent ev, PGresult *res)
  {
  	int i;
  	const char *s;
  	struct List *el, *tmp;
  	struct PgDatabase *db;
  
  	switch (ev) {
  	case PGS_CONNECT_OK:
  		pgs_send_query_simple(sk, "select datname from pg_database"
  				     	 " where not datistemplate and datallowconn");
  		break;
  	case PGS_RESULT_OK:
  		/* tag old dbs as dead */
  		statlist_for_each(el, &database_list) {
  			db = container_of(el, struct PgDatabase, head);
  			db->dropped = true;
  		}
  		/* process new dbs */
  		for (i = 0; i < PQntuples(res); i++) {
  			s = PQgetvalue(res, i, 0);
  			launch_db(s);
  		}
  		/* drop old dbs */
  		statlist_for_each_safe(el, &database_list, tmp) {
  			db = container_of(el, struct PgDatabase, head);
  			if (db->dropped)
  				drop_db(db, true);
  		}
  		pgs_disconnect(sk);
  		pgs_sleep(sk, cf.check_period);
  		break;
  	case PGS_TIMEOUT:
  		detect_dbs();
  		break;
  	default:
  		pgs_disconnect(sk);
  		pgs_sleep(sk, cf.check_period);
  	}
  }
  
  static void detect_dbs(void)
  {
  	if (!db_template) {
  		const char *cstr = make_connstr(cf.initial_database);
  		db_template = pgs_create(cstr, detect_handler, NULL);
  	}
  	pgs_connect(db_template);
  }
  
  static bool launch_db_cb(void *arg, const char *db)
  {
  	launch_db(db);
  	return true;
  }
  
  static void recheck_dbs(void)
  {
  	struct PgDatabase *db;
  	struct List *el, *tmp;
  	if (cf.database_list && cf.database_list[0]) {
  		/* tag old dbs as dead */
  		statlist_for_each(el, &database_list) {
  			db = container_of(el, struct PgDatabase, head);
  			db->dropped = true;
  		}
  		/* process new ones */
  		if (!parse_word_list(cf.database_list, launch_db_cb, NULL)) {
  			log_warning("database_list parsing failed: %s", strerror(errno));
  			return;
  		}
  		/* drop old ones */
  		statlist_for_each_safe(el, &database_list, tmp) {
  			db = container_of(el, struct PgDatabase, head);
  			if (db->dropped)
  				drop_db(db, true);
  		}
  
  		/* done with template for the moment */
  		if (db_template) {
  			pgs_free(db_template);
  			db_template = NULL;
  		}
  	} else if (!db_template) {
  		log_info("auto-detecting dbs ...");
  		detect_dbs();
  	}
  }
  
  static struct event stats_ev;
  
  static void stats_handler(int fd, short flags, void *arg)
  {
  	struct timeval tv = { cf.stats_period, 0 };
  
  	log_info("{ticks: %d, maint: %d, retry: %d}",
  		 stats.n_ticks, stats.n_maint, stats.n_retry);
  	memset(&stats, 0, sizeof(stats));
  
  	if (evtimer_add(&stats_ev, &tv) < 0)
  		fatal_perror("evtimer_add");
  }
  
  static void stats_setup(void)
  {
  	struct timeval tv = { cf.stats_period, 0 };
  	evtimer_set(&stats_ev, stats_handler, NULL);
  	if (evtimer_add(&stats_ev, &tv) < 0)
  		fatal_perror("evtimer_add");
  }
  
  static void cleanup(void)
  {
  	struct PgDatabase *db;
  	struct List *elem, *tmp;
  
  	statlist_for_each_safe(elem, &database_list, tmp) {
  		db = container_of(elem, struct PgDatabase, head);
  		drop_db(db, false);
  	}
  	pgs_free(db_template);
  
  	event_base_free(NULL);
  	reset_logging();
  }
  
  static void main_loop_once(void)
  {
  	reset_time_cache();
  	if (event_loop(EVLOOP_ONCE) != 0) {
  		log_error("event_loop failed: %s", strerror(errno));
  	}
  }
  
  int main(int argc, char *argv[])
  {
  	int c;
  	bool daemon = false;
  	int sig = 0;
  	const char *signame = NULL;
  
  	for (c = 1; c < argc; c++) {
  		if (!strcmp(argv[c], "--ini")) {
  			printf("%s", sample_ini);
  			exit(0);
  		}
  		if (!strcmp(argv[c], "--help")) {
  			printf(usage_str);
  			exit(0);
  		}
  	}
  
  	while ((c = getopt(argc, argv, "dqvhVrsk")) != -1) {
  		switch (c) {
  		case 'd':
  			daemon = true;
  			break;
  		case 'v':
  			cf_verbose++;
  			break;
  		case 'q':
  			cf_quiet = 1;
  			break;
  		case 'h':
  			printf(usage_str);
  			return 0;
  #ifdef SIGHUP
  		case 'r':
  			sig = SIGHUP;
  			signame = "SIGHUP";
  			break;
  #endif
  		case 's':
  			sig = SIGINT;
  			signame = "SIGINT";
  			break;
  		case 'k':
  			sig = SIGTERM;
  			signame = "SIGTERM";
  			break;
  		default:
  			printf("bad switch: ");
  			printf(usage_str);
  			return 1;
  		}
  	}
  	if (optind + 1 != argc) {
  		fprintf(stderr, "pgqd requires config file\n");
  		return 1;
  	}
  
  	cf.config_file = argv[optind];
  
  	load_config();
  	conf_info.loaded = true;
  
  	if (sig) {
  		if (!cf.pidfile || !cf.pidfile[0]) {
  			fprintf(stderr, "No pidfile configured\n");
  			return 1;
  		}
  		if (signal_pidfile(cf.pidfile, sig))
  			fprintf(stderr, "%s sent\n", signame);
  		else
  			fprintf(stderr, "Old process is not running\n");
  		return 0;
  	}
  
  	log_info("Starting pgqd " PACKAGE_VERSION);
  
  	daemonize(cf.pidfile, daemon);
  
  	if (!event_init())
  		fatal("event_init failed");
  
  	signal_setup();
  
  	stats_setup();
  
  	recheck_dbs();
  
  	while (!got_sigint)
  		main_loop_once();
  
  	cleanup();
  
  	return 0;
  }
  