text: /**
   * @file timemaster.c
   * @brief Program to run NTP with PTP as reference clocks.
   * @note Copyright (C) 2014 Miroslav Lichvar <mlichvar@redhat.com>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License along
   * with this program; if not, write to the Free Software Foundation, Inc.,
   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   */
  
  #include <ctype.h>
  #include <errno.h>
  #include <libgen.h>
  #include <limits.h>
  #include <linux/net_tstamp.h>
  #include <signal.h>
  #include <spawn.h>
  #include <stdarg.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/stat.h>
  #include <sys/types.h>
  #include <sys/wait.h>
  #include <unistd.h>
  
  #include "print.h"
  #include "sk.h"
  #include "util.h"
  #include "version.h"
  
  #define DEFAULT_RUNDIR "/var/run/timemaster"
  
  #define DEFAULT_FIRST_SHM_SEGMENT 0
  
  #define DEFAULT_NTP_PROGRAM CHRONYD
  #define DEFAULT_NTP_MINPOLL 6
  #define DEFAULT_NTP_MAXPOLL 10
  #define DEFAULT_PTP_DELAY 1e-4
  #define DEFAULT_PTP_NTP_POLL 2
  #define DEFAULT_PTP_PHC2SYS_POLL 0
  
  #define DEFAULT_CHRONYD_SETTINGS \
  	"makestep 1 3"
  #define DEFAULT_NTPD_SETTINGS \
  	"restrict default nomodify notrap nopeer noquery", \
  	"restrict 127.0.0.1", \
  	"restrict ::1"
  #define DEFAULT_PTP4L_OPTIONS "-l", "5"
  #define DEFAULT_PHC2SYS_OPTIONS "-l", "5"
  
  enum source_type {
  	NTP_SERVER,
  	PTP_DOMAIN,
  };
  
  enum ntp_program {
  	CHRONYD,
  	NTPD,
  };
  
  struct ntp_server {
  	char *address;
  	int minpoll;
  	int maxpoll;
  	int iburst;
  	char *ntp_options;
  };
  
  struct ptp_domain {
  	int domain;
  	int ntp_poll;
  	int phc2sys_poll;
  	double delay;
  	char **interfaces;
  	char **ptp4l_settings;
  	char *ntp_options;
  };
  
  struct source {
  	enum source_type type;
  	union {
  		struct ntp_server ntp;
  		struct ptp_domain ptp;
  	};
  };
  
  struct program_config {
  	char *path;
  	char **options;
  	char **settings;
  };
  
  struct timemaster_config {
  	struct source **sources;
  	enum ntp_program ntp_program;
  	char *rundir;
  	int first_shm_segment;
  	struct program_config chronyd;
  	struct program_config ntpd;
  	struct program_config phc2sys;
  	struct program_config ptp4l;
  };
  
  struct config_file {
  	char *path;
  	char *content;
  };
  
  struct script {
  	struct config_file **configs;
  	char ***commands;
  };
  
  static void free_parray(void **a)
  {
  	void **p;
  
  	for (p = a; *p; p++)
  		free(*p);
  	free(a);
  }
  
  static void extend_string_array(char ***a, char **strings)
  {
  	char **s;
  
  	for (s = strings; *s; s++)
  		parray_append((void ***)a, xstrdup(*s));
  }
  
  static void extend_config_string(char **s, char **lines)
  {
  	for (; *lines; lines++)
  		string_appendf(s, "%s\n", *lines);
  }
  
  static int parse_bool(char *s, int *b)
  {
  	if (get_ranged_int(s, b, 0, 1) != PARSED_OK)
  		return 1;
  
  	return 0;
  }
  
  static int parse_int(char *s, int *i)
  {
  	if (get_ranged_int(s, i, INT_MIN, INT_MAX) != PARSED_OK)
  		return 1;
  
  	return 0;
  }
  
  static int parse_double(char *s, double *d)
  {
  	if (get_ranged_double(s, d, INT_MIN, INT_MAX) != PARSED_OK)
  		return 1;
  
  	return 0;
  }
  
  static char *parse_word(char *s)
  {
  	while (*s && !isspace(*s))
  		s++;
  	while (*s && isspace(*s))
  		*(s++) = '\0';
  	return s;
  }
  
  static void parse_words(char *s, char ***a)
  {
  	char *w;
  
  	if (**a) {
  		free_parray((void **)(*a));
  		*a = (char **)parray_new();
  	}
  	while (*s) {
  		w = s;
  		s = parse_word(s);
  		parray_append((void ***)a, xstrdup(w));
  	}
  }
  
  static void replace_string(char *s, char **str)
  {
  	if (*str)
  		free(*str);
  	*str = xstrdup(s);
  }
  
  static char *parse_section_name(char *s)
  {
  	char *s1, *s2;
  
  	s1 = s + 1;
  	for (s2 = s1; *s2 && *s2 != ']'; s2++)
  		;
  	*s2 = '\0';
  
  	return xstrdup(s1);
  }
  
  static void parse_setting(char *s, char **name, char **value)
  {
  	*name = s;
  	for (*value = s; **value && !isspace(**value); (*value)++)
  		;
  	for (; **value && !isspace(**value); (*value)++)
  		;
  	for (; **value && isspace(**value); (*value)++)
  		**value = '\0';
  }
  
  static void source_destroy(struct source *source)
  {
  	switch (source->type) {
  	case NTP_SERVER:
  		free(source->ntp.address);
  		free(source->ntp.ntp_options);
  		break;
  	case PTP_DOMAIN:
  		free_parray((void **)source->ptp.interfaces);
  		free_parray((void **)source->ptp.ptp4l_settings);
  		free(source->ptp.ntp_options);
  		break;
  	}
  	free(source);
  }
  
  static struct source *source_ntp_parse(char *parameter, char **settings)
  {
  	char *name, *value;
  	struct source *source;
  	int r = 0;
  
  	if (!*parameter) {
  		pr_err("missing address for ntp_server");
  		return NULL;
  	}
  
  	source = xmalloc(sizeof(*source));
  	source->type = NTP_SERVER;
  	source->ntp.address = xstrdup(parameter);
  	source->ntp.minpoll = DEFAULT_NTP_MINPOLL;
  	source->ntp.maxpoll = DEFAULT_NTP_MAXPOLL;
  	source->ntp.iburst = 0;
  	source->ntp.ntp_options = xstrdup("");
  
  	for (; *settings; settings++) {
  		parse_setting(*settings, &name, &value);
  		if (!strcasecmp(name, "minpoll")) {
  			r = parse_int(value, &source->ntp.minpoll);
  		} else if (!strcasecmp(name, "maxpoll")) {
  			r = parse_int(value, &source->ntp.maxpoll);
  		} else if (!strcasecmp(name, "iburst")) {
  			r = parse_bool(value, &source->ntp.iburst);
  		} else if (!strcasecmp(name, "ntp_options")) {
  			replace_string(value, &source->ntp.ntp_options);
  		} else {
  			pr_err("unknown ntp_server setting %s", name);
  			goto failed;
  		}
  		if (r) {
  			pr_err("invalid value %s for %s", value, name);
  			goto failed;
  		}
  	}
  
  	return source;
  failed:
  	source_destroy(source);
  	return NULL;
  }
  
  static struct source *source_ptp_parse(char *parameter, char **settings)
  {
  	char *name, *value;
  	struct source *source;
  	int r = 0;
  
  	source = xmalloc(sizeof(*source));
  	source->type = PTP_DOMAIN;
  	source->ptp.delay = DEFAULT_PTP_DELAY;
  	source->ptp.ntp_poll = DEFAULT_PTP_NTP_POLL;
  	source->ptp.phc2sys_poll = DEFAULT_PTP_PHC2SYS_POLL;
  	source->ptp.interfaces = (char **)parray_new();
  	source->ptp.ptp4l_settings = (char **)parray_new();
  	source->ptp.ntp_options = xstrdup("");
  
  	if (parse_int(parameter, &source->ptp.domain)) {
  		pr_err("invalid ptp_domain number %s", parameter);
  		goto failed;
  	}
  
  	for (; *settings; settings++) {
  		parse_setting(*settings, &name, &value);
  		if (!strcasecmp(name, "delay")) {
  			r = parse_double(value, &source->ptp.delay);
  		} else if (!strcasecmp(name, "ntp_poll")) {
  			r = parse_int(value, &source->ptp.ntp_poll);
  		} else if (!strcasecmp(name, "phc2sys_poll")) {
  			r = parse_int(value, &source->ptp.phc2sys_poll);
  		} else if (!strcasecmp(name, "ptp4l_option")) {
  			parray_append((void ***)&source->ptp.ptp4l_settings,
  				      xstrdup(value));
  		} else if (!strcasecmp(name, "ntp_options")) {
  			replace_string(value, &source->ptp.ntp_options);
  		} else if (!strcasecmp(name, "interfaces")) {
  			parse_words(value, &source->ptp.interfaces);
  		} else {
  			pr_err("unknown ptp_domain setting %s", name);
  			goto failed;
  		}
  
  		if (r) {
  			pr_err("invalid value %s for %s", value, name);
  			goto failed;
  		}
  	}
  
  	if (!*source->ptp.interfaces) {
  		pr_err("no interfaces specified for ptp_domain %d",
  		       source->ptp.domain);
  		goto failed;
  	}
  
  	return source;
  failed:
  	source_destroy(source);
  	return NULL;
  }
  
  static int parse_program_settings(char **settings,
  				  struct program_config *config)
  {
  	char *name, *value;
  
  	for (; *settings; settings++) {
  		parse_setting(*settings, &name, &value);
  		if (!strcasecmp(name, "path")) {
  			replace_string(value, &config->path);
  		} else if (!strcasecmp(name, "options")) {
  			parse_words(value, &config->options);
  		} else {
  			pr_err("unknown program setting %s", name);
  			return 1;
  		}
  	}
  
  	return 0;
  }
  
  static int parse_timemaster_settings(char **settings,
  				     struct timemaster_config *config)
  {
  	char *name, *value;
  	int r = 0;
  
  	for (; *settings; settings++) {
  		parse_setting(*settings, &name, &value);
  		if (!strcasecmp(name, "ntp_program")) {
  			if (!strcasecmp(value, "chronyd")) {
  				config->ntp_program = CHRONYD;
  			} else if (!strcasecmp(value, "ntpd")) {
  				config->ntp_program = NTPD;
  			} else {
  				pr_err("unknown ntp program %s", value);
  				return 1;
  			}
  		} else if (!strcasecmp(name, "rundir")) {
  			replace_string(value, &config->rundir);
  		} else if (!strcasecmp(name, "first_shm_segment")) {
  			r = parse_int(value, &config->first_shm_segment);
  		} else {
  			pr_err("unknown timemaster setting %s", name);
  			return 1;
  		}
  		if (r) {
  			pr_err("invalid value %s for %s", value, name);
  			return 1;
  		}
  	}
  
  	return 0;
  }
  
  static int parse_section(char **settings, char *name,
  			 struct timemaster_config *config)
  {
  	struct source *source = NULL;
  	char ***settings_dst = NULL;
  	char *parameter = parse_word(name);
  
  	if (!strcasecmp(name, "ntp_server")) {
  		source = source_ntp_parse(parameter, settings);
  		if (!source)
  			return 1;
  	} else if (!strcasecmp(name, "ptp_domain")) {
  		source = source_ptp_parse(parameter, settings);
  		if (!source)
  			return 1;
  	} else if (!strcasecmp(name, "chrony.conf")) {
  		settings_dst = &config->chronyd.settings;
  	} else if (!strcasecmp(name, "ntp.conf")) {
  		settings_dst = &config->ntpd.settings;
  	} else if (!strcasecmp(name, "ptp4l.conf")) {
  		settings_dst = &config->ptp4l.settings;
  	} else if (!strcasecmp(name, "chronyd")) {
  		if (parse_program_settings(settings, &config->chronyd))
  			return 1;
  	} else if (!strcasecmp(name, "ntpd")) {
  		if (parse_program_settings(settings, &config->ntpd))
  			return 1;
  	} else if (!strcasecmp(name, "phc2sys")) {
  		if (parse_program_settings(settings, &config->phc2sys))
  			return 1;
  	} else if (!strcasecmp(name, "ptp4l")) {
  		if (parse_program_settings(settings, &config->ptp4l))
  			return 1;
  	} else if (!strcasecmp(name, "timemaster")) {
  		if (parse_timemaster_settings(settings, config))
  			return 1;
  	} else {
  		pr_err("unknown section %s", name);
  		return 1;
  	}
  
  	if (source)
  		parray_append((void ***)&config->sources, source);
  
  	if (settings_dst) {
  		free_parray((void **)*settings_dst);
  		*settings_dst = (char **)parray_new();
  		extend_string_array(settings_dst, settings);
  	}
  
  	return 0;
  }
  
  static void init_program_config(struct program_config *config,
  				const char *name, ...)
  {
  	const char *s;
  	va_list ap;
  
  	config->path = xstrdup(name);
  	config->settings = (char **)parray_new();
  	config->options = (char **)parray_new();
  
  	va_start(ap, name);
  
  	/* add default options and settings */
  	while ((s = va_arg(ap, const char *)))
  		parray_append((void ***)&config->options, xstrdup(s));
  	while ((s = va_arg(ap, const char *)))
  		parray_append((void ***)&config->settings, xstrdup(s));
  
  	va_end(ap);
  }
  
  static void free_program_config(struct program_config *config)
  {
  	free(config->path);
  	free_parray((void **)config->settings);
  	free_parray((void **)config->options);
  }
  
  static void config_destroy(struct timemaster_config *config)
  {
  	struct source **sources;
  
  	for (sources = config->sources; *sources; sources++)
  		source_destroy(*sources);
  	free(config->sources);
  
  	free_program_config(&config->chronyd);
  	free_program_config(&config->ntpd);
  	free_program_config(&config->phc2sys);
  	free_program_config(&config->ptp4l);
  
  	free(config->rundir);
  	free(config);
  }
  
  static struct timemaster_config *config_parse(char *path)
  {
  	struct timemaster_config *config = xcalloc(1, sizeof(*config));
  	FILE *f;
  	char buf[4096], *line, *section_name = NULL;
  	char **section_lines = NULL;
  	int ret = 0;
  
  	config->sources = (struct source **)parray_new();
  	config->ntp_program = DEFAULT_NTP_PROGRAM;
  	config->rundir = xstrdup(DEFAULT_RUNDIR);
  	config->first_shm_segment = DEFAULT_FIRST_SHM_SEGMENT;
  
  	init_program_config(&config->chronyd, "chronyd",
  			    NULL, DEFAULT_CHRONYD_SETTINGS, NULL);
  	init_program_config(&config->ntpd, "ntpd",
  			    NULL, DEFAULT_NTPD_SETTINGS, NULL);
  	init_program_config(&config->phc2sys, "phc2sys",
  			    DEFAULT_PHC2SYS_OPTIONS, NULL, NULL);
  	init_program_config(&config->ptp4l, "ptp4l",
  			    DEFAULT_PTP4L_OPTIONS, NULL, NULL);
  
  	f = fopen(path, "r");
  	if (!f) {
  		pr_err("failed to open %s: %m", path);
  		free(config);
  		return NULL;
  	}
  
  	while (fgets(buf, sizeof(buf), f)) {
  		/* remove trailing and leading whitespace */
  		for (line = buf + strlen(buf) - 1;
  		     line >= buf && isspace(*line); line--)
  			*line = '\0';
  		for (line = buf; *line && isspace(*line); line++)
  			;
  		/* skip comments and empty lines */
  		if (!*line || *line == '#')
  			continue;
  
  		if (*line == '[') {
  			/* parse previous section before starting another */
  			if (section_name) {
  				if (parse_section(section_lines, section_name,
  						  config)) {
  					ret = 1;
  					break;
  				}
  				free_parray((void **)section_lines);
  				free(section_name);
  			}
  			section_name = parse_section_name(line);
  			section_lines = (char **)parray_new();
  			continue;
  		}
  
  		if (!section_lines) {
  			pr_err("settings outside section");
  			ret = 1;
  			break;
  		}
  
  		parray_append((void ***)&section_lines, xstrdup(line));
  	}
  
  	if (!ret && section_name &&
  	    parse_section(section_lines, section_name, config)) {
  		ret = 1;
  	}
  
  	fclose(f);
  
  	if (section_name)
  		free(section_name);
  	if (section_lines)
  		free_parray((void **)section_lines);
  
  	if (ret) {
  		config_destroy(config);
  		return NULL;
  	}
  
  	return config;
  }
  
  static char **get_ptp4l_command(struct program_config *config,
  				struct config_file *file, char **interfaces,
  				int hw_ts)
  {
  	char **command = (char **)parray_new();
  
  	parray_append((void ***)&command, xstrdup(config->path));
  	extend_string_array(&command, config->options);
  	parray_extend((void ***)&command,
  		      xstrdup("-f"), xstrdup(file->path),
  		      xstrdup(hw_ts ? "-H" : "-S"), NULL);
  
  	for (; *interfaces; interfaces++)
  		parray_extend((void ***)&command,
  			      xstrdup("-i"), xstrdup(*interfaces), NULL);
  
  	return command;
  }
  
  static char **get_phc2sys_command(struct program_config *config, int domain,
  				  int poll, int shm_segment, char *uds_path)
  {
  	char **command = (char **)parray_new();
  
  	parray_append((void ***)&command, xstrdup(config->path));
  	extend_string_array(&command, config->options);
  	parray_extend((void ***)&command,
  		      xstrdup("-a"), xstrdup("-r"),
  		      xstrdup("-R"), string_newf("%.2f", poll > 0 ?
  						1.0 / (1 << poll) : 1 << -poll),
  		      xstrdup("-z"), xstrdup(uds_path),
  		      xstrdup("-n"), string_newf("%d", domain),
  		      xstrdup("-E"), xstrdup("ntpshm"),
  		      xstrdup("-M"), string_newf("%d", shm_segment), NULL);
  
  	return command;
  }
  
  static char *get_refid(char *prefix, unsigned int number)
  {
  	if (number < 10)
  		return string_newf("%.3s%u", prefix, number);
  	else if (number < 100)
  		return string_newf("%.2s%u", prefix, number);
  	else if (number < 1000)
  		return string_newf("%.1s%u", prefix, number);
  	return NULL;
  };
  
  static void add_shm_source(int shm_segment, int poll, int dpoll, double delay,
  			   char *ntp_options, char *prefix,
  			   struct timemaster_config *config, char **ntp_config)
  {
  	char *refid = get_refid(prefix, shm_segment);
  
  	switch (config->ntp_program) {
  	case CHRONYD:
  		string_appendf(ntp_config,
  			       "refclock SHM %d poll %d dpoll %d "
  			       "refid %s precision 1.0e-9 delay %.1e %s\n",
  			       shm_segment, poll, dpoll, refid, delay,
  			       ntp_options);
  		break;
  	case NTPD:
  		string_appendf(ntp_config,
  			       "server 127.127.28.%d minpoll %d maxpoll %d "
  			       "mode 1 %s\n"
  			       "fudge 127.127.28.%d refid %s\n",
  			       shm_segment, poll, poll, ntp_options,
  			       shm_segment, refid);
  		break;
  	}
  
  	free(refid);
  }
  
  static int add_ntp_source(struct ntp_server *source, char **ntp_config)
  {
  	pr_debug("adding NTP server %s", source->address);
  
  	string_appendf(ntp_config, "server %s minpoll %d maxpoll %d %s %s\n",
  		       source->address, source->minpoll, source->maxpoll,
  		       source->iburst ? "iburst" : "", source->ntp_options);
  	return 0;
  }
  
  static int add_ptp_source(struct ptp_domain *source,
  			  struct timemaster_config *config, int *shm_segment,
  			  int ***allocated_phcs, char **ntp_config,
  			  struct script *script)
  {
  	struct config_file *config_file;
  	char **command, *uds_path, **interfaces;
  	int i, j, num_interfaces, *phc, *phcs, hw_ts;
  	struct sk_ts_info ts_info;
  
  	pr_debug("adding PTP domain %d", source->domain);
  
  	hw_ts = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE |
  		SOF_TIMESTAMPING_RAW_HARDWARE;
  
  	for (num_interfaces = 0;
  	     source->interfaces[num_interfaces]; num_interfaces++)
  		;
  
  	if (!num_interfaces)
  		return 0;
  
  	/* get PHCs used by specified interfaces */
  	phcs = xmalloc(num_interfaces * sizeof(int));
  	for (i = 0; i < num_interfaces; i++) {
  		phcs[i] = -1;
  
  		/* check if the interface has a usable PHC */
  		if (sk_get_ts_info(source->interfaces[i], &ts_info)) {
  			pr_err("failed to get time stamping info for %s",
  			       source->interfaces[i]);
  			free(phcs);
  			return 1;
  		}
  
  		if (!ts_info.valid ||
  		    ((ts_info.so_timestamping & hw_ts) != hw_ts)) {
  			pr_debug("interface %s: no PHC", source->interfaces[i]);
  			continue;
  		}
  
  		pr_debug("interface %s: PHC %d", source->interfaces[i],
  			 ts_info.phc_index);
  
  		/* and the PHC isn't already used in another source */
  		for (j = 0; (*allocated_phcs)[j]; j++) {
  			if (*(*allocated_phcs)[j] == ts_info.phc_index) {
  				pr_debug("PHC %d already allocated",
  					 ts_info.phc_index);
  				break;
  			}
  		}
  		if (!(*allocated_phcs)[j])
  			phcs[i] = ts_info.phc_index;
  	}
  
  	for (i = 0; i < num_interfaces; i++) {
  		/* skip if already used by ptp4l in this domain */
  		if (phcs[i] == -2)
  			continue;
  
  		interfaces = (char **)parray_new();
  		parray_append((void ***)&interfaces, source->interfaces[i]);
  
  		/* merge all interfaces sharing PHC to one ptp4l command */
  		if (phcs[i] >= 0) {
  			for (j = i + 1; j < num_interfaces; j++) {
  				if (phcs[i] == phcs[j]) {
  					parray_append((void ***)&interfaces,
  						      source->interfaces[j]);
  					/* mark the interface as used */
  					phcs[j] = -2;
  				}
  			}
  
  			/* don't use this PHC in other sources */
  			phc = xmalloc(sizeof(int));
  			*phc = phcs[i];
  			parray_append((void ***)allocated_phcs, phc);
  		}
  
  		uds_path = string_newf("%s/ptp4l.%d.socket",
  				       config->rundir, *shm_segment);
  
  		config_file = xmalloc(sizeof(*config_file));
  		config_file->path = string_newf("%s/ptp4l.%d.conf",
  						config->rundir, *shm_segment);
  		config_file->content = xstrdup("[global]\n");
  		extend_config_string(&config_file->content,
  				     config->ptp4l.settings);
  		extend_config_string(&config_file->content,
  				     source->ptp4l_settings);
  		string_appendf(&config_file->content,
  			       "slaveOnly 1\n"
  			       "domainNumber %d\n"
  			       "uds_address %s\n",
  			       source->domain, uds_path);
  
  		if (phcs[i] >= 0) {
  			/* HW time stamping */
  			command = get_ptp4l_command(&config->ptp4l, config_file,
  						    interfaces, 1);
  			parray_append((void ***)&script->commands, command);
  
  			command = get_phc2sys_command(&config->phc2sys,
  						      source->domain,
  						      source->phc2sys_poll,
  						      *shm_segment, uds_path);
  			parray_append((void ***)&script->commands, command);
  		} else {
  			/* SW time stamping */
  			command = get_ptp4l_command(&config->ptp4l, config_file,
  						    interfaces, 0);
  			parray_append((void ***)&script->commands, command);
  
  			string_appendf(&config_file->content,
  				       "clock_servo ntpshm\n"
  				       "ntpshm_segment %d\n", *shm_segment);
  		}
  
  		parray_append((void ***)&script->configs, config_file);
  
  		add_shm_source(*shm_segment, source->ntp_poll,
  			       source->phc2sys_poll, source->delay,
  			       source->ntp_options, "PTP", config, ntp_config);
  
  		(*shm_segment)++;
  
  		free(uds_path);
  		free(interfaces);
  	}
  
  	free(phcs);
  
  	return 0;
  }
  
  static char **get_chronyd_command(struct program_config *config,
  				  struct config_file *file)
  {
  	char **command = (char **)parray_new();
  
  	parray_append((void ***)&command, xstrdup(config->path));
  	extend_string_array(&command, config->options);
  	parray_extend((void ***)&command, xstrdup("-n"),
  		      xstrdup("-f"), xstrdup(file->path), NULL);
  
  	return command;
  }
  
  static char **get_ntpd_command(struct program_config *config,
  			       struct config_file *file)
  {
  	char **command = (char **)parray_new();
  
  	parray_append((void ***)&command, xstrdup(config->path));
  	extend_string_array(&command, config->options);
  	parray_extend((void ***)&command, xstrdup("-n"),
  		      xstrdup("-c"), xstrdup(file->path), NULL);
  
  	return command;
  }
  
  static struct config_file *add_ntp_program(struct timemaster_config *config,
  					   struct script *script)
  {
  	struct config_file *ntp_config = xmalloc(sizeof(*ntp_config));
  	char **command = NULL;
  
  	ntp_config->content = xstrdup("");
  
  	switch (config->ntp_program) {
  	case CHRONYD:
  		extend_config_string(&ntp_config->content,
  				     config->chronyd.settings);
  		ntp_config->path = string_newf("%s/chrony.conf",
  					       config->rundir);
  		command = get_chronyd_command(&config->chronyd, ntp_config);
  		break;
  	case NTPD:
  		extend_config_string(&ntp_config->content,
  				     config->ntpd.settings);
  		ntp_config->path = string_newf("%s/ntp.conf", config->rundir);
  		command = get_ntpd_command(&config->ntpd, ntp_config);
  		break;
  	}
  
  	parray_append((void ***)&script->configs, ntp_config);
  	parray_append((void ***)&script->commands, command);
  
  	return ntp_config;
  }
  
  static void script_destroy(struct script *script)
  {
  	char ***commands, **command;
  	struct config_file *config, **configs;
  
  	for (configs = script->configs; *configs; configs++) {
  		config = *configs;
  		free(config->path);
  		free(config->content);
  		free(config);
  	}
  	free(script->configs);
  
  	for (commands = script->commands; *commands; commands++) {
  		for (command = *commands; *command; command++)
  			free(*command);
  		free(*commands);
  	}
  	free(script->commands);
  
  	free(script);
  }
  
  static struct script *script_create(struct timemaster_config *config)
  {
  	struct script *script = xmalloc(sizeof(*script));
  	struct source *source, **sources;
  	struct config_file *ntp_config = NULL;
  	int **allocated_phcs = (int **)parray_new();
  	int ret = 0, shm_segment;
  
  	script->configs = (struct config_file **)parray_new();
  	script->commands = (char ***)parray_new();
  
  	ntp_config = add_ntp_program(config, script);
  	shm_segment = config->first_shm_segment;
  
  	for (sources = config->sources; (source = *sources); sources++) {
  		switch (source->type) {
  		case NTP_SERVER:
  			if (add_ntp_source(&source->ntp, &ntp_config->content))
  				ret = 1;
  			break;
  		case PTP_DOMAIN:
  			if (add_ptp_source(&source->ptp, config, &shm_segment,
  					   &allocated_phcs,
  					   &ntp_config->content, script))
  				ret = 1;
  			break;
  		}
  	}
  
  	free_parray((void **)allocated_phcs);
  
  	if (ret) {
  		script_destroy(script);
  		return NULL;
  	}
  
  	return script;
  }
  
  static pid_t start_program(char **command, sigset_t *mask)
  {
  	char **arg, *s;
  	pid_t pid;
  
  #ifdef HAVE_POSIX_SPAWN
  	posix_spawnattr_t attr;
  
  	if (posix_spawnattr_init(&attr)) {
  		pr_err("failed to init spawn attributes: %m");
  		return 0;
  	}
  
  	if (posix_spawnattr_setsigmask(&attr, mask) ||
  	    posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK) ||
  	    posix_spawnp(&pid, command[0], NULL, &attr, command, environ)) {
  		pr_err("failed to spawn %s: %m", command[0]);
  		posix_spawnattr_destroy(&attr);
  		return 0;
  	}
  
  	posix_spawnattr_destroy(&attr);
  #else
  	pid = fork();
  
  	if (pid < 0) {
  		pr_err("fork() failed: %m");
  		return 0;
  	}
  
  	if (!pid) {
  		/* restore the signal mask */
  		if (sigprocmask(SIG_SETMASK, mask, NULL) < 0) {
  			pr_err("sigprocmask() failed: %m");
  			exit(100);
  		}
  
  		execvp(command[0], (char **)command);
  
  		pr_err("failed to execute %s: %m", command[0]);
  
  		exit(101);
  	}
  #endif
  
  	for (s = xstrdup(""), arg = command; *arg; arg++)
  		string_appendf(&s, "%s ", *arg);
  
  	pr_info("process %d started: %s", pid, s);
  
  	free(s);
  
  	return pid;
  }
  
  static int create_config_files(struct config_file **configs)
  {
  	struct config_file *config;
  	FILE *file;
  	char *tmp, *dir;
  	struct stat st;
  
  	for (; (config = *configs); configs++) {
  		tmp = xstrdup(config->path);
  		dir = dirname(tmp);
  		if (stat(dir, &st) < 0 && errno == ENOENT &&
  		    mkdir(dir, 0755) < 0) {
  			pr_err("failed to create %s: %m", dir);
  			free(tmp);
  			return 1;
  		}
  		free(tmp);
  
  		pr_debug("creating %s", config->path);
  
  		file = fopen(config->path, "w");
  		if (!file) {
  			pr_err("failed to open %s: %m", config->path);
  			return 1;
  		}
  
  		if (fwrite(config->content,
  			   strlen(config->content), 1, file) != 1) {
  			pr_err("failed to write to %s", config->path);
  			fclose(file);
  			return 1;
  		}
  
  		fclose(file);
  	}
  
  	return 0;
  }
  
  static int remove_config_files(struct config_file **configs)
  {
  	struct config_file *config;
  
  	for (; (config = *configs); configs++) {
  		pr_debug("removing %s", config->path);
  
  		if (unlink(config->path))
  			pr_err("failed to remove %s: %m", config->path);
  	}
  
  	return 0;
  }
  
  static int script_run(struct script *script)
  {
  	sigset_t mask, old_mask;
  	siginfo_t info;
  	pid_t pid, *pids;
  	int i, num_commands, status, ret = 0;
  
  	for (num_commands = 0; script->commands[num_commands]; num_commands++)
  		;
  
  	if (!num_commands) {
  		/* nothing to do */
  		return 0;
  	}
  
  	if (create_config_files(script->configs))
  		return 1;
  
  	sigemptyset(&mask);
  	sigaddset(&mask, SIGCHLD);
  	sigaddset(&mask, SIGTERM);
  	sigaddset(&mask, SIGQUIT);
  	sigaddset(&mask, SIGINT);
  
  	/* block the signals */
  	if (sigprocmask(SIG_BLOCK, &mask, &old_mask) < 0) {
  		pr_err("sigprocmask() failed: %m");
  		return 1;
  	}
  
  	pids = xcalloc(num_commands, sizeof(*pids));
  
  	for (i = 0; i < num_commands; i++) {
  		pids[i] = start_program(script->commands[i], &old_mask);
  		if (!pids[i]) {
  			kill(getpid(), SIGTERM);
  			break;
  		}
  	}
  
  	/* wait for one of the blocked signals */
  	while (1) {
  		if (sigwaitinfo(&mask, &info) < 0) {
  			if (errno == EINTR)
  				continue;
  			pr_err("sigwaitinfo() failed: %m");
  			break;
  		}
  
  		/*
  		 * assume only the first process (i.e. chronyd or ntpd) is
  		 * essential and continue if other processes terminate
  		 */
  		if (info.si_signo == SIGCHLD && info.si_pid != pids[0]) {
  			pr_info("process %d terminated (ignored)", info.si_pid);
  			continue;
  		}
  
  		pr_info("received signal %d", info.si_signo);
  		break;
  	}
  
  	/* kill all started processes */
  	for (i = 0; i < num_commands; i++) {
  		if (pids[i] > 0) {
  			pr_debug("killing process %d", pids[i]);
  			kill(pids[i], SIGTERM);
  		}
  	}
  
  	while ((pid = wait(&status)) >= 0) {
  		if (!WIFEXITED(status)) {
  			pr_info("process %d terminated abnormally", pid);
  			ret = 1;
  		} else {
  			if (WEXITSTATUS(status))
  				ret = 1;
  			pr_info("process %d terminated with status %d", pid,
  				WEXITSTATUS(status));
  		}
  	}
  
  	free(pids);
  
  	if (remove_config_files(script->configs))
  		return 1;
  
  	return ret;
  }
  
  static void script_print(struct script *script)
  {
  	char ***commands, **command;
  	struct config_file *config, **configs;
  
  	for (configs = script->configs; *configs; configs++) {
  		config = *configs;
  		fprintf(stderr, "%s:\n\n%s\n", config->path, config->content);
  	}
  
  	fprintf(stderr, "commands:\n\n");
  	for (commands = script->commands; *commands; commands++) {
  		for (command = *commands; *command; command++)
  			fprintf(stderr, "%s ", *command);
  		fprintf(stderr, "\n");
  	}
  }
  
  static void usage(char *progname)
  {
  	fprintf(stderr,
  		"\nusage: %s [options] -f file\n\n"
  		" -f file      specify path to configuration file\n"
  		" -n           only print generated files and commands\n"
  		" -l level     set logging level (6)\n"
  		" -m           print messages to stdout\n"
  		" -q           do not print messages to syslog\n"
  		" -v           print version and exit\n"
  		" -h           print this message and exit\n",
  		progname);
  }
  
  int main(int argc, char **argv)
  {
  	struct timemaster_config *config;
  	struct script *script;
  	char *progname, *config_path = NULL;
  	int c, ret = 0, log_stdout = 0, log_syslog = 1, dry_run = 0;
  
  	progname = strrchr(argv[0], '/');
  	progname = progname ? progname + 1 : argv[0];
  
  	print_set_progname(progname);
  	print_set_verbose(1);
  	print_set_syslog(0);
  
  	while (EOF != (c = getopt(argc, argv, "f:nl:mqvh"))) {
  		switch (c) {
  		case 'f':
  			config_path = optarg;
  			break;
  		case 'n':
  			dry_run = 1;
  			break;
  		case 'l':
  			print_set_level(atoi(optarg));
  			break;
  		case 'm':
  			log_stdout = 1;
  			break;
  		case 'q':
  			log_syslog = 0;
  			break;
  		case 'v':
  			version_show(stdout);
  			return 0;
  		case 'h':
  			usage(progname);
  			return 0;
  		default:
  			usage(progname);
  			return 1;
  		}
  	}
  
  	if (!config_path) {
  		pr_err("no configuration file specified");
  		return 1;
  	}
  
  	config = config_parse(config_path);
  	if (!config)
  		return 1;
  
  	script = script_create(config);
  	config_destroy(config);
  	if (!script)
  		return 1;
  
  	print_set_verbose(log_stdout);
  	print_set_syslog(log_syslog);
  
  	if (dry_run)
  		script_print(script);
  	else
  		ret = script_run(script);
  
  	script_destroy(script);
  
  	if (!dry_run)
  		pr_info("exiting");
  
  	return ret;
  }
  