timemaster is a program that uses ptp4l and phc2sys in combination with chronyd or ntpd to synchronize the system clock to NTP and PTP time sources. The PTP time is provided by phc2sys and ptp4l via SHM reference clocks to chronyd/ntpd, which can compare all time sources and use the best sources to synchronize the system clock. Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
1174 lines
27 KiB
C
1174 lines
27 KiB
C
/**
|
|
* @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_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;
|
|
};
|
|
|
|
struct ptp_domain {
|
|
int domain;
|
|
int ntp_poll;
|
|
int phc2sys_poll;
|
|
double delay;
|
|
char **interfaces;
|
|
char **ptp4l_settings;
|
|
};
|
|
|
|
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;
|
|
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, strdup(*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, strdup(w));
|
|
}
|
|
}
|
|
|
|
static void replace_string(char *s, char **str)
|
|
{
|
|
if (*str)
|
|
free(*str);
|
|
*str = strdup(s);
|
|
}
|
|
|
|
static char *parse_section_name(char *s)
|
|
{
|
|
char *s1, *s2;
|
|
|
|
s1 = s + 1;
|
|
for (s2 = s1; *s2 && *s2 != ']'; s2++)
|
|
;
|
|
*s2 = '\0';
|
|
|
|
return strdup(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);
|
|
break;
|
|
case PTP_DOMAIN:
|
|
free_parray((void **)source->ptp.interfaces);
|
|
free_parray((void **)source->ptp.ptp4l_settings);
|
|
break;
|
|
}
|
|
free(source);
|
|
}
|
|
|
|
static struct source *source_ntp_parse(char *parameter, char **settings)
|
|
{
|
|
char *name, *value;
|
|
struct ntp_server ntp_server;
|
|
struct source *source;
|
|
int r = 0;
|
|
|
|
if (!*parameter) {
|
|
pr_err("missing address for ntp_server");
|
|
return NULL;
|
|
}
|
|
|
|
ntp_server.address = parameter;
|
|
ntp_server.minpoll = DEFAULT_NTP_MINPOLL;
|
|
ntp_server.maxpoll = DEFAULT_NTP_MAXPOLL;
|
|
ntp_server.iburst = 0;
|
|
|
|
for (; *settings; settings++) {
|
|
parse_setting(*settings, &name, &value);
|
|
if (!strcasecmp(name, "minpoll")) {
|
|
r = parse_int(value, &ntp_server.minpoll);
|
|
} else if (!strcasecmp(name, "maxpoll")) {
|
|
r = parse_int(value, &ntp_server.maxpoll);
|
|
} else if (!strcasecmp(name, "iburst")) {
|
|
r = parse_bool(value, &ntp_server.iburst);
|
|
} else {
|
|
pr_err("unknown ntp_server setting %s", name);
|
|
return NULL;
|
|
}
|
|
if (r) {
|
|
pr_err("invalid value %s for %s", value, name);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
source = malloc(sizeof(*source));
|
|
source->type = NTP_SERVER;
|
|
source->ntp = ntp_server;
|
|
source->ntp.address = strdup(source->ntp.address);
|
|
|
|
return source;
|
|
}
|
|
|
|
static struct source *source_ptp_parse(char *parameter, char **settings)
|
|
{
|
|
char *name, *value;
|
|
struct source *source;
|
|
int r = 0;
|
|
|
|
source = malloc(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();
|
|
|
|
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,
|
|
strdup(value));
|
|
} 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;
|
|
|
|
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 {
|
|
pr_err("unknown timemaster setting %s", 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 = strdup(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, strdup(s));
|
|
while ((s = va_arg(ap, const char *)))
|
|
parray_append((void ***)&config->settings, strdup(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 = calloc(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 = strdup(DEFAULT_RUNDIR);
|
|
|
|
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 ***)§ion_lines, strdup(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, strdup(config->path));
|
|
extend_string_array(&command, config->options);
|
|
parray_extend((void ***)&command,
|
|
strdup("-f"), strdup(file->path),
|
|
strdup(hw_ts ? "-H" : "-S"), NULL);
|
|
|
|
for (; *interfaces; interfaces++)
|
|
parray_extend((void ***)&command,
|
|
strdup("-i"), strdup(*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, strdup(config->path));
|
|
extend_string_array(&command, config->options);
|
|
parray_extend((void ***)&command,
|
|
strdup("-a"), strdup("-r"),
|
|
strdup("-R"), string_newf("%.2f", poll > 0 ?
|
|
1.0 / (1 << poll) : 1 << -poll),
|
|
strdup("-z"), strdup(uds_path),
|
|
strdup("-n"), string_newf("%d", domain),
|
|
strdup("-E"), strdup("ntpshm"),
|
|
strdup("-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 *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\n",
|
|
shm_segment, poll, dpoll, refid, delay);
|
|
break;
|
|
case NTPD:
|
|
string_appendf(ntp_config,
|
|
"server 127.127.28.%d minpoll %d maxpoll %d\n"
|
|
"fudge 127.127.28.%d refid %s\n",
|
|
shm_segment, poll, poll, 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\n",
|
|
source->address, source->minpoll, source->maxpoll,
|
|
source->iburst ? " iburst" : "");
|
|
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 = malloc(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 = malloc(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 = malloc(sizeof(*config_file));
|
|
config_file->path = string_newf("%s/ptp4l.%d.conf",
|
|
config->rundir, *shm_segment);
|
|
config_file->content = strdup("[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, "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, strdup(config->path));
|
|
extend_string_array(&command, config->options);
|
|
parray_extend((void ***)&command, strdup("-n"),
|
|
strdup("-f"), strdup(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, strdup(config->path));
|
|
extend_string_array(&command, config->options);
|
|
parray_extend((void ***)&command, strdup("-n"),
|
|
strdup("-c"), strdup(file->path), NULL);
|
|
|
|
return command;
|
|
}
|
|
|
|
static struct config_file *add_ntp_program(struct timemaster_config *config,
|
|
struct script *script)
|
|
{
|
|
struct config_file *ntp_config = malloc(sizeof(*ntp_config));
|
|
char **command = NULL;
|
|
|
|
ntp_config->content = strdup("");
|
|
|
|
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 = malloc(sizeof(*script));
|
|
struct source *source, **sources;
|
|
struct config_file *ntp_config = NULL;
|
|
int **allocated_phcs = (int **)parray_new();
|
|
int ret = 0, shm_segment = 0;
|
|
|
|
script->configs = (struct config_file **)parray_new();
|
|
script->commands = (char ***)parray_new();
|
|
|
|
ntp_config = add_ntp_program(config, script);
|
|
|
|
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 int 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 1;
|
|
}
|
|
|
|
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 1;
|
|
}
|
|
|
|
posix_spawnattr_destroy(&attr);
|
|
#else
|
|
pid = fork();
|
|
|
|
if (pid < 0) {
|
|
pr_err("fork() failed: %m");
|
|
return 1;
|
|
}
|
|
|
|
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 = strdup(""), arg = command; *arg; arg++)
|
|
string_appendf(&s, "%s ", *arg);
|
|
|
|
pr_info("process %d started: %s", pid, s);
|
|
|
|
free(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 = strdup(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;
|
|
int status, ret = 0;
|
|
char ***command;
|
|
|
|
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;
|
|
}
|
|
|
|
for (command = script->commands; *command; command++) {
|
|
if (start_program(*command, &old_mask)) {
|
|
kill(getpid(), SIGTERM);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* wait for one of the blocked signals */
|
|
while (1) {
|
|
if (sigwaitinfo(&mask, &info) > 0)
|
|
break;
|
|
if (errno != EINTR) {
|
|
pr_err("sigwaitinfo() failed: %m");
|
|
break;
|
|
}
|
|
}
|
|
|
|
pr_info("received signal %d", info.si_signo);
|
|
|
|
/* kill the process group */
|
|
kill(0, 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));
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|