Discussion:
[Linuxptp-devel] [PATCH 0/3] timemaster
Miroslav Lichvar
2014-07-25 15:15:29 UTC
Permalink
This is the program I was talking about earlier. It starts ptp4l and
phc2sys in combination with chronyd or ntpd to synchronize the system
clock to NTP and PTP sources. The functionality reminded me a bit of
the postfix master daemon, hence time master :). I'm open to
suggestions for a better name.

The first patch seems to be needed to avoid deadlocks in the
ptp4l/phc2sys signal handler when terminating the program by CTRL-C
and the second patch adds some utility functions used in the third
patch.

Miroslav Lichvar (3):
Don't print messages in signal handler.
Add string and pointer array utility functions.
Add timemaster.

makefile | 7 +-
timemaster.8 | 335 +++++++++++++++++
timemaster.c | 1144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
util.c | 96 ++++-
util.h | 57 +++
5 files changed, 1636 insertions(+), 3 deletions(-)
create mode 100644 timemaster.8
create mode 100644 timemaster.c
--
1.9.3
Miroslav Lichvar
2014-07-25 15:15:31 UTC
Permalink
Add some functions to work with strings and arrays of pointers that will
be useful later.

Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
util.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
util.h | 57 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 152 insertions(+)

diff --git a/util.c b/util.c
index cb428b1..06c3296 100644
--- a/util.c
+++ b/util.c
@@ -18,6 +18,7 @@
*/
#include <errno.h>
#include <signal.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -342,3 +343,97 @@ int is_running(void)
{
return running;
}
+
+char *string_newf(const char *format, ...)
+{
+ va_list ap;
+ char *s;
+
+ va_start(ap, format);
+ if (vasprintf(&s, format, ap) < 0)
+ s = NULL;
+ va_end(ap);
+
+ return s;
+}
+
+void string_append(char **s, const char *str)
+{
+ size_t len1, len2;
+
+ len1 = strlen(*s);
+ len2 = strlen(str);
+ *s = realloc(*s, len1 + len2 + 1);
+ if (*s)
+ memcpy((*s) + len1, str, len2 + 1);
+}
+
+void string_appendf(char **s, const char *format, ...)
+{
+ va_list ap;
+ size_t len1, len2;
+ char *s2;
+
+ len1 = strlen(*s);
+
+ va_start(ap, format);
+ len2 = vasprintf(&s2, format, ap);
+ va_end(ap);
+
+ if (len2 < 0) {
+ *s = NULL;
+ return;
+ }
+
+ *s = realloc(*s, len1 + len2 + 1);
+ if (*s)
+ memcpy((*s) + len1, s2, len2 + 1);
+ free(s2);
+}
+
+void **parray_new(void)
+{
+ void **a = malloc(sizeof(*a));
+
+ if (a)
+ *a = NULL;
+ return a;
+}
+
+void parray_append(void ***a, void *p)
+{
+ parray_extend(a, p, NULL);
+}
+
+void parray_extend(void ***a, ...)
+{
+ va_list ap;
+ int ilen, len, alloced;
+ void *p;
+
+ for (len = 0; (*a)[len]; len++)
+ ;
+ len++;
+
+ va_start(ap, a);
+ for (ilen = 0; va_arg(ap, void *); ilen++)
+ ;
+ va_end(ap);
+
+ /* Reallocate in exponentially increasing sizes. */
+ for (alloced = 1; alloced < len; alloced <<= 1)
+ ;
+ if (alloced < len + ilen) {
+ while (alloced < len + ilen)
+ alloced *= 2;
+ *a = realloc(*a, alloced * sizeof **a);
+ if (!*a)
+ return;
+ }
+
+ va_start(ap, a);
+ while ((p = va_arg(ap, void *)))
+ (*a)[len++ - 1] = p;
+ va_end(ap);
+ (*a)[len - 1] = NULL;
+}
diff --git a/util.h b/util.h
index cf05e49..c3900aa 100644
--- a/util.h
+++ b/util.h
@@ -226,4 +226,61 @@ int handle_term_signals(void);
*/
int is_running(void);

+/**
+ * Get an allocated and formatted string. This is a wrapper around asprintf().
+ *
+ * @param format printf() format string.
+ * @param ... printf() arguments.
+ * @return Pointer to the allocated string, NULL on error.
+ */
+#ifdef __GNUC__
+__attribute__ ((format (printf, 1, 2)))
+#endif
+char *string_newf(const char *format, ...);
+
+/**
+ * Reallocate a string and append another string to it.
+ *
+ * @param s String that should be extended, set to NULL on error.
+ * @param str String appended to s.
+ */
+void string_append(char **s, const char *str);
+#ifdef __GNUC__
+__attribute__ ((format (printf, 2, 3)))
+#endif
+/**
+ * Reallocate a string and append a formatted string to it.
+ *
+ * @param s String that should be extended, set to NULL on error.
+ * @param format printf() format string.
+ * @param ... printf() arguments.
+ */
+void string_appendf(char **s, const char *format, ...);
+
+/**
+ * Get an empty array of pointers terminated by NULL.
+ *
+ * @return Pointer to the allocated array, NULL on error.
+ */
+void **parray_new(void);
+
+/**
+ * Append pointer to a NULL-terminated pointer array. The array is reallocated
+ * in exponentially increasing sizes.
+ *
+ * @param a Pointer to pointer array, set to NULL on error.
+ * @param p Pointer appended to the array.
+ */
+void parray_append(void ***a, void *p);
+
+
+/**
+ * Append pointers to a NULL-terminated pointer array. The array is reallocated
+ * in exponentially increasing sizes.
+ *
+ * @param a Pointer to pointer array, set to NULL on error.
+ * @param ... NULL-terminated list of pointers.
+ */
+void parray_extend(void ***a, ...);
+
#endif
--
1.9.3
Miroslav Lichvar
2014-07-25 15:15:30 UTC
Permalink
Only reentrant functions should be called here.

Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
util.c | 1 -
1 file changed, 1 deletion(-)

diff --git a/util.c b/util.c
index ae66bb1..cb428b1 100644
--- a/util.c
+++ b/util.c
@@ -318,7 +318,6 @@ int get_arg_val_d(int op, const char *optarg, double *val,

static void handle_int_quit_term(int s)
{
- pr_notice("caught signal %d", s);
running = 0;
}
--
1.9.3
Miroslav Lichvar
2014-07-25 15:15:32 UTC
Permalink
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 <***@redhat.com>
---
makefile | 7 +-
timemaster.8 | 335 +++++++++++++++++
timemaster.c | 1144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1484 insertions(+), 2 deletions(-)
create mode 100644 timemaster.8
create mode 100644 timemaster.c

diff --git a/makefile b/makefile
index 74a7fe2..aa5c2ca 100644
--- a/makefile
+++ b/makefile
@@ -22,13 +22,14 @@ CC = $(CROSS_COMPILE)gcc
VER = -DVER=$(version)
CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG) $(EXTRA_CFLAGS)
LDLIBS = -lm -lrt $(EXTRA_LDFLAGS)
-PRG = ptp4l pmc phc2sys hwstamp_ctl phc_ctl
+PRG = ptp4l pmc phc2sys hwstamp_ctl phc_ctl timemaster
OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o fault.o \
filter.o fsm.o linreg.o mave.o mmedian.o msg.o ntpshm.o phc.o \
pi.o port.o print.o ptp4l.o raw.o servo.o sk.o stats.o tlv.o \
transport.o udp.o udp6.o uds.o util.o version.o

-OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o pmc.o pmc_common.o sysoff.o phc_ctl.o
+OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o phc_ctl.o pmc.o pmc_common.o \
+ sysoff.o timemaster.o
SRC = $(OBJECTS:.o=.c)
DEPEND = $(OBJECTS:.o=.d)
srcdir := $(dir $(lastword $(MAKEFILE_LIST)))
@@ -56,6 +57,8 @@ hwstamp_ctl: hwstamp_ctl.o version.o

phc_ctl: phc_ctl.o phc.o sk.o util.o clockadj.o sysoff.o print.o version.o

+timemaster: print.o sk.o timemaster.o util.o version.o
+
version.o: .version version.sh $(filter-out version.d,$(DEPEND))

.version: force
diff --git a/timemaster.8 b/timemaster.8
new file mode 100644
index 0000000..79fab13
--- /dev/null
+++ b/timemaster.8
@@ -0,0 +1,335 @@
+.TH TIMEMASTER 8 "July 2014" "linuxptp"
+.SH NAME
+
+timemaster \- run NTP with PTP as reference clocks
+
+.SH SYNOPSIS
+
+.B timemaster
+[
+.B \-nmqv
+] [
+.BI \-l " print-level"
+]
+.BI \-f " file"
+
+.SH DESCRIPTION
+\fBtimemaster\fR is a program that uses \fBptp4l\fR and \fBphc2sys\fR in
+combination with \fBchronyd\fR or \fBntpd\fR to synchronize the system clock to
+NTP and PTP time sources. The PTP time is provided by \fBphc2sys\fR and
+\fBptp4l\fR via SHM reference clocks to \fBchronyd\fR/\fBntpd\fR, which
+can compare all time sources and use the best sources to synchronize the system
+clock.
+
+On start, \fBtimemaster\fR reads a configuration file that specifies the NTP
+and PTP time sources, checks which network interfaces have and share a PTP
+hardware clock (PHC), generates configuration files for \fBptp4l\fR and
+\fBchronyd\fR/\fBntpd\fR, and start the \fBptp4l\fR, \fBphc2sys\fR,
+\fBchronyd\fR/\fBntpd\fR processes as needed. Then, it waits for a signal to
+kill the processes, remove the generated configuration files and exit.
+
+.SH OPTIONS
+
+.TP
+.BI \-f " file"
+Specify the path to the \fBtimemaster\fR configuration file.
+.TP
+.BI \-n
+Don't start the programs, only print their configuration files and the commands
+that would be executed if this option wasn't specified.
+.TP
+.BI \-l " level"
+Set the maximum syslog level of messages which should be printed or sent to
+the system logger. The default value is 6 (LOG_INFO).
+.TP
+.B \-m
+Print messages to the standard output.
+.TP
+.B \-q
+Don't send messages to the system logger.
+.TP
+.B \-v
+Print the software version and exit.
+.TP
+.BI \-h
+Display a help message and exit.
+
+.SH CONFIGURATION FILE
+
+The configuration file is divided into sections. Each section starts with a
+line containing its name enclosed in brackets and it follows with settings.
+Each setting is placed on a separate line, it contains the name of the
+option and the value separated by whitespace characters. Empty lines and lines
+starting with # are ignored.
+
+Sections that can used in the configuration file and options that can be set in
+them are described below.
+
+.SS [timemaster]
+
+.TP
+.B ntp_program
+Select which NTP implementation should be used. Possible values are
+\fBchronyd\fR and \fBntpd\fR. The default value is \fBchronyd\fR. Limitations
+of the implementations relevant to the timemaster configuration are listed in
+\fBNOTES\fR.
+
+.TP
+.B rundir
+Specify the directory where should be generated \fBchronyd\fR, \fBntpd\fR and
+\fBptp4l\fR configuration files and sockets. The directory will be created if
+it doesn't exist. The default value is \fB/var/run/timemaster\fR.
+
+.SS [ntp_server address]
+
+The \fBntp_server\fR section specifies an NTP server that should be used as a
+time source. The address of the server is included in the name of the section.
+
+.TP
+.B minpoll
+.TQ
+.B maxpoll
+Specify the minimum and maximum NTP polling interval as powers of two in
+seconds. The default values are 6 (64 seconds) and 10 (1024 seconds)
+respectively. Shorter polling intervals usually improve the accuracy
+significantly, but they should be used only when allowed by the operators of
+the NTP service (public NTP servers generally don't allow too frequent
+queries). If the NTP server is located on the same LAN, polling intervals
+around 4 (16 seconds) might give best accuracy.
+
+.TP
+.B iburst
+Enable or disable sending a burst of NTP packets on start to speed up the
+initial synchronization. Possible values are 1 and 0. The default value is 0
+(disabled).
+
+.SS [ptp_domain number]
+
+The \fBptp_domain\fR section specifies a PTP domain that should be used as a
+time source. The PTP domain number is included in the name of the section. The
+\fBptp4l\fR instances are configured to run in the \fBslaveOnly\fR mode. In
+this section at least the \fBinterfaces\fR option needs to be set, other
+options are optional.
+
+.TP
+.B interfaces
+Specify which network interfaces should be used for this PTP domain. A separate
+\fBptp4l\fR instance will be started for each group of interfaces sharing the
+same PHC and for each interface that supports only SW time stamping. HW time
+stamping is enabled automatically. If an interface with HW time stamping is
+specified also in other PTP domains, only the \fBptp4l\fR instance from the
+first PTP domain will be using HW time stamping.
+
+.TP
+.B ntp_poll
+Specify the polling interval of the NTP SHM reference clock reading samples
+from \fBptp4l\fR or \fBphc2sys\fR. It's specified as a power of two in seconds.
+The default value is 2 (4 seconds).
+
+.TP
+.B phc2sys_poll
+Specify the polling interval used by \fBphc2sys\fR to read a PTP clock
+synchronized by \fBptp4l\fR and update the SHM sample for
+\fBchronyd\fR/\fBntpd\fR. It's specified as a power of two in seconds. The
+default value is 0 (1 second).
+
+.TP
+.B delay
+Specify the maximum assumed roundtrip delay to the primary source of the time
+in this PTP domain. This value is included in the distance used by
+\fBchronyd\fR in the source selection algorithm to detect falsetickers and
+assign weights for source combining. The default value is 1e-4 (100
+microseconds). With \fBntpd\fR, the \fBtos mindist\fR command can be used to
+set a limit with similar purpose globally for all time sources.
+
+.TP
+.B ptp4l_option
+Specify an extra \fBptp4l\fR option specific to this PTP domain that should be
+added to the configuration files generated for \fBptp4l\fR. This option may be
+used multiple times in one \fBptp_domain\fR section.
+
+.SS [chronyd]
+
+.TP
+.B path
+Specify the path to the \fBchronyd\fR binary. The default value is
+\fBchronyd\fR to search for the binary in $PATH.
+
+.TP
+.B options
+Specify extra options that should be added to the \fBchronyd\fR command line.
+No extra options are added by default.
+
+.SS [chrony.conf]
+
+Settings specified in this section are copied directly to the configuration
+file generated for \fBchronyd\fR. If this section is not present in the
+\fBtimemaster\fR configuration file, the following setting will be added:
+
+.EX
+makestep 1 3
+.EE
+
+This configures \fBchronyd\fR to step the system clock in the first three
+updates if the offset is larger than 1 second.
+
+.SS [ntpd]
+
+.TP
+.B path
+Specify the path to the \fBntpd\fR binary. The default value is \fBntpd\fR to
+search for the binary in $PATH.
+
+.TP
+.B options
+Specify extra options that should be added to the \fBntpd\fR command line. No
+extra options are added by default.
+
+.SS [ntp.conf]
+
+Settings specified in this section are copied directly to the configuration
+file generated for \fBntpd\fR. If this section is not present in the
+\fBtimemaster\fR configuration file, the following settings will be added:
+
+.EX
+restrict default nomodify notrap nopeer noquery
+restrict 127.0.0.1
+restrict ::1
+.EE
+
+This configures \fBntpd\fR to use safe default restrictions.
+
+.SS [phc2sys]
+
+.TP
+.B path
+Specify the path to the \fBphc2sys\fR binary. The default value is
+\fBphc2sys\fR to search for the binary in $PATH.
+
+.TP
+.B options
+Specify extra options that should be added to all \fBphc2sys\fR command lines.
+By default, \fB-l 5\fR is added to the command lines.
+
+.SS [ptp4l]
+
+.TP
+.B path
+Specify the path to the \fBptp4l\fR binary. The default value is \fBptp4l\fR to
+search for the binary in $PATH.
+
+.TP
+.B options
+Specify extra options that should be added to all \fBptp4l\fR command lines. By
+default, \fB-l 5\fR is added to the command lines.
+
+.SS [ptp4l.conf]
+Settings specified in this section are copied directly to the configuration
+files generated for all \fBptp4l\fR instances. There is no default content of
+this section.
+
+.SH NOTES
+For best accuracy, \fBchronyd\fR is usually preferred over \fBntpd\fR, it also
+synchronizes the system clock faster. Both NTP implementations, however, have
+some limitations that need to be considered before choosing the one to be used
+in in given \fBtimemaster\fR configuration.
+
+The \fBchronyd\fR limitations are:
+
+.RS
+The maximum number of reference clocks used at the same time is 8. This limits
+the number of PHCs and interfaces using SW time stamping that can be used for
+PTP.
+
+Using polling intervals (\fBminpoll\fR, \fBmaxpoll\fR, \fBntp_poll\fR options)
+shorter than 2 (4 seconds) is not recommended with versions before 1.30. With
+1.30 and later values of 0 or 1 can be used for NTP sources and negative values
+for PTP sources (\fBntp_poll\fR) to specify a subsecond interval.
+.RE
+
+The \fBntpd\fR limitations are:
+
+.RS
+Only the first two shared-memory segments created by the SHM refclock driver
+in \fBntpd\fR have owner-only access. Other segments are created with world
+access, possibly allowing any user on the system writing to the segments and
+disrupting the synchronization.
+
+The shortest polling interval for all sources is 3 (8 seconds).
+
+Nanosecond resolution in the SHM refclock driver is supported in version
+4.2.7p303 and later, older versions have only microsecond resolution.
+.RE
+
+.SH EXAMPLES
+
+A minimal configuration file using one NTP source and two PTP sources would be:
+
+.EX
+[ntp_server 10.1.1.1]
+
+[ptp_domain 0]
+interfaces eth0
+
+[ptp_domain 1]
+interfaces eth1
+.EE
+
+A more complex example using all \fBtimemaster\fR options would be:
+
+.EX
+[ntp_server 10.1.1.1]
+minpoll 3
+maxpoll 4
+iburst 1
+
+[ptp_domain 0]
+interfaces eth0 eth1
+ntp_poll 0
+phc2sys_poll -2
+delay 10e-6
+ptp4l_option clock_servo linreg
+ptp4l_option delay_mechanism P2P
+
+[timemaster]
+ntp_program chronyd
+rundir /var/run/timemaster
+
+[chronyd]
+path /usr/sbin/chronyd
+options
+
+[chrony.conf]
+makestep 1 3
+logchange 0.5
+rtcsync
+driftfile /var/lib/chrony/drift
+
+[ntpd]
+path /usr/sbin/ntpd
+options -u ntp:ntp
+
+[ntp.conf]
+restrict default nomodify notrap nopeer noquery
+restrict 127.0.0.1
+restrict ::1
+driftfile /var/lib/ntp/drift
+
+[phc2sys]
+path /usr/sbin/phc2sys
+options -l 5
+
+[ptp4l]
+path /usr/sbin/ptp4l
+options
+
+[ptp4l.conf]
+logging_level 5
+.EE
+
+.SH SEE ALSO
+
+.BR chronyd (8),
+.BR ntpd (8),
+.BR phc2sys (8),
+.BR ptp4l (8)
diff --git a/timemaster.c b/timemaster.c
new file mode 100644
index 0000000..5c6431a
--- /dev/null
+++ b/timemaster.c
@@ -0,0 +1,1144 @@
+/**
+ * @file timemaster.c
+ * @brief Program to run NTP with PTP as reference clocks.
+ * @note Copyright (C) 2014 Miroslav Lichvar <***@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 <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 ***)&section_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++)
+ ;
+
+ /* 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(*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++) {
+ command = *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 = fork();
+
+ if (pid < 0) {
+ pr_err("fork() failed: %m");
+ return 1;
+ } else if (pid)
+ return 0;
+
+ /* restore the signal mask */
+ if (sigprocmask(SIG_SETMASK, mask, NULL) < 0) {
+ pr_err("sigprocmask() failed");
+ exit(100);
+ }
+
+ for (s = strdup(""), arg = command; *arg; arg++)
+ string_appendf(&s, "%s ", *arg);
+ pr_info("running (pid %d): %s", getpid(), s);
+ free(s);
+
+ execvp(command[0], (char **)command);
+
+ pr_err("failed to exec %s: %m", command[0]);
+
+ exit(101);
+}
+
+static int create_config_files(struct config_file **configs)
+{
+ struct config_file *config;
+ FILE *file;
+ char *tmp, *dir;
+ struct stat st;
+
+ for (config = *configs; (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; (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;
+}
--
1.9.3
Richard Cochran
2014-09-21 10:57:33 UTC
Permalink
This has some warnings,

/home/richard/git/linuxptp/util.c: In function ‘string_newf’:
/home/richard/git/linuxptp/util.c:353:2: error: implicit declaration of function ‘vasprintf’ [-Werror=implicit-function-declaration]

/home/richard/git/linuxptp/timemaster.c: In function 'start_program':
/home/richard/git/linuxptp/timemaster.c:899:2: error: implicit declaration of function 'fork' [-Werror=implicit-function-declaration]

Thanks,
Richard
Miroslav Lichvar
2014-09-25 15:21:39 UTC
Permalink
Post by Richard Cochran
This has some warnings,
/home/richard/git/linuxptp/util.c:353:2: error: implicit declaration of function ‘vasprintf’ [-Werror=implicit-function-declaration]
Is libc on your system missing vasprintf() or it just needs
_GNU_SOURCE to be defined?
Post by Richard Cochran
/home/richard/git/linuxptp/timemaster.c:899:2: error: implicit declaration of function 'fork' [-Werror=implicit-function-declaration]
Hm, timemaster.c includes <unistd.h>, so I'm not sure what's wrong
here. Any suggestions?

After rebasing to current git there is also a conflict with the new
functions declared in config.h, I'll add a patch that separates util.h
from config.h to allow compilation of timemaster.c.

Thanks,
--
Miroslav Lichvar
Richard Cochran
2014-09-25 16:53:32 UTC
Permalink
Post by Miroslav Lichvar
Post by Richard Cochran
This has some warnings,
/home/richard/git/linuxptp/util.c:353:2: error: implicit declaration of function ‘vasprintf’ [-Werror=implicit-function-declaration]
Is libc on your system missing vasprintf() or it just needs
_GNU_SOURCE to be defined?
Yup.
Post by Miroslav Lichvar
Post by Richard Cochran
/home/richard/git/linuxptp/timemaster.c:899:2: error: implicit declaration of function 'fork' [-Werror=implicit-function-declaration]
Hm, timemaster.c includes <unistd.h>, so I'm not sure what's wrong
here. Any suggestions?
I guess this was my coldfire build -> uClinux, no fork.

I'll double check this later...

Thanks,
Richard
Miroslav Lichvar
2014-09-30 14:21:48 UTC
Permalink
Post by Richard Cochran
Post by Miroslav Lichvar
Is libc on your system missing vasprintf() or it just needs
_GNU_SOURCE to be defined?
Yup.
Which of two is it? :) Should I rework the code to not use the
function?
Post by Richard Cochran
I guess this was my coldfire build -> uClinux, no fork.
I'll double check this later...
Interesting, I'm curious how programs are normally started there. I
can add a check to incdefs.sh and build timemaster only if fork() is
available, would that be ok?

Thanks,
--
Miroslav Lichvar
Richard Cochran
2014-09-30 14:38:48 UTC
Permalink
Post by Miroslav Lichvar
Post by Richard Cochran
Post by Miroslav Lichvar
Is libc on your system missing vasprintf() or it just needs
_GNU_SOURCE to be defined?
Yup.
Which of two is it? :) Should I rework the code to not use the
function?
Sorry, it was _GNU_SOURCE missing in my case.
Post by Miroslav Lichvar
Post by Richard Cochran
I guess this was my coldfire build -> uClinux, no fork.
I'll double check this later...
Interesting, I'm curious how programs are normally started there.
They use vfork instead.
Post by Miroslav Lichvar
I
can add a check to incdefs.sh and build timemaster only if fork() is
available, would that be ok?
Okay, or maybe just keep timemaster out of the "all" target?

Thanks,
Richard
Miroslav Lichvar
2014-10-01 10:22:36 UTC
Permalink
Post by Richard Cochran
Post by Miroslav Lichvar
I
can add a check to incdefs.sh and build timemaster only if fork() is
available, would that be ok?
Okay, or maybe just keep timemaster out of the "all" target?
Hm, I'd like to see all binaries build by default on normal systems
that have fork.

How about using posix_spawn() instead? It seems uclibc includes
support for it, so we could build timemaster even there (although I'm
not sure how useful it would actually be). I just tried it with glibc
and it seems to be working nicely.
--
Miroslav Lichvar
Richard Cochran
2014-10-01 14:56:49 UTC
Permalink
Post by Miroslav Lichvar
Hm, I'd like to see all binaries build by default on normal systems
that have fork.
Ok.
Post by Miroslav Lichvar
How about using posix_spawn() instead? It seems uclibc includes
support for it, so we could build timemaster even there (although I'm
not sure how useful it would actually be). I just tried it with glibc
and it seems to be working nicely.
I'll try it. But if it doesn't work, don't worry about it.

For me, no-mmu is just a "nice to have", and it should not hold back
linuxptp development.

Thanks,
Richard

Loading...