diff --git a/stages/org.osbuild.chrony b/stages/org.osbuild.chrony index 2274a47e..11cec065 100755 --- a/stages/org.osbuild.chrony +++ b/stages/org.osbuild.chrony @@ -54,6 +54,112 @@ def handle_leapsectz(chrony_conf_lines, timezone): chrony_conf_lines[:] = [f"leapsectz {timezone}"] + chrony_conf_lines +def handle_refclocks(chrony_conf_lines, refclocks): + new_lines = [] + + for refclock in refclocks: + driver = refclock["driver"] + driver_name = driver["name"] + new_line = "refclock" + driver_options = "" + if driver_name == "PPS": + driver_options = handle_refclock_pps(driver) + elif driver_name == "SHM": + driver_options = handle_refclock_shm(driver) + elif driver_name == "SOCK": + driver_options = handle_refclock_sock(driver) + elif driver_name == "PHC": + driver_options = handle_refclock_phc(driver) + else: + # this should be caught by the schema + raise ValueError(f"unknown refclock driver {driver_name}") + + new_line += " " + driver_options + + # append general reflock options + refclock_options = [] + poll = refclock.get("poll") + if poll is not None: + refclock_options.append(f"poll {poll}") + + dpoll = refclock.get("dpoll") + if dpoll is not None: + refclock_options.append(f"dpoll {dpoll}") + + offset = refclock.get("offset") + if offset is not None: + refclock_options.append(f"offset {offset}") + + if refclock_options: + gen_options_str = " ".join(refclock_options) + new_line += " " + gen_options_str + + new_lines.append(new_line) + + chrony_conf_lines[:] = new_lines + chrony_conf_lines + + +def handle_refclock_pps(driver): + device = driver["device"] + line = f"PPS {device}" + + options = [] + if driver.get("clear"): + options.append("clear") + + if options: + options_str = ",".join(options) + line += f":{options_str}" + + return line + + +def handle_refclock_shm(driver): + segment = driver["segment"] + line = f"SHM {segment}" + + options = [] + perm = driver.get("perm") + if perm is not None: + options.append(f"perm={perm}") + + if options: + options_str = ",".join(options) + line += f":{options_str}" + + return line + + +def handle_refclock_sock(driver): + path = driver["path"] + return f"SOCK {path}" + + +def handle_refclock_phc(driver): + path = driver["path"] + line = f"PHC {path}" + + options = [] + if driver.get("nocrossts"): + options.append("nocrossts") + if driver.get("extpps"): + options.append("extpps") + pin = driver.get("pin") + if pin is not None: + options.append(f"pin={pin}") + channel = driver.get("channel") + if channel is not None: + options.append(f"channel={channel}") + if driver.get("clear"): + options.append("clear") + + if options: + options_str = ",".join(options) + line += f":{options_str}" + + return line + + def main(tree, options): timeservers = options.get("timeservers", []) servers = options.get("servers", []) @@ -61,6 +167,8 @@ def main(tree, options): # therefore default to 'None' to distinguish these two cases. leapsectz = options.get("leapsectz", None) + refclocks = options.get("refclocks", []) + with open(f"{tree}/etc/chrony.conf", encoding="utf8") as f: chrony_conf = f.read() @@ -76,6 +184,8 @@ def main(tree, options): handle_servers(lines, servers) if leapsectz is not None: handle_leapsectz(lines, leapsectz) + if refclocks: + handle_refclocks(lines, refclocks) new_chrony_conf = "\n".join(lines) diff --git a/stages/org.osbuild.chrony.meta.json b/stages/org.osbuild.chrony.meta.json index caf72909..2229e87c 100644 --- a/stages/org.osbuild.chrony.meta.json +++ b/stages/org.osbuild.chrony.meta.json @@ -19,28 +19,19 @@ " - 'maxpoll'", " - 'iburst' (defaults to true)", " - 'prefer' (defaults to false)", + "", "The `leapsectz` option configures chrony behavior related to automatic checking", "of the next occurrence of the leap second, using the provided timezone. Its", "value is a string representing a timezone from the system tz database (e.g.", "'right/UTC'). If an empty string is provided, then all occurrences of", "`leapsectz` directive are removed from the configuration.", - "Constraints:", - " - Exactly one of 'timeservers' or 'servers' options must be provided." + "", + "The refclock directive can be used to specify one or more hardware", + "reference clocks to be used as a time source." ], "schema": { "additionalProperties": false, - "oneOf": [ - { - "required": [ - "timeservers" - ] - }, - { - "required": [ - "servers" - ] - } - ], + "minProperties": 1, "properties": { "timeservers": { "type": "array", @@ -90,6 +81,164 @@ "leapsectz": { "type": "string", "description": "Timezone used by chronyd to determine when will the next leap second occur. Empty value will remove the option." + }, + "refclocks": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "driver" + ], + "properties": { + "driver": { + "oneOf": [ + { + "$ref": "#/definitions/PPS" + }, + { + "$ref": "#/definitions/SHM" + }, + { + "$ref": "#/definitions/SOCK" + }, + { + "$ref": "#/definitions/PHC" + } + ] + }, + "poll": { + "type": "integer", + "description": "Specifies the interval between processing times of timestamps as a power of 2 in seconds." + }, + "dpoll": { + "type": "integer", + "description": "Some drivers do not listen for external events and try to produce samples in their own polling interval. This is defined as a power of 2 and can be negative to specify a sub-second interval. The default is 0 (1 second)." + }, + "offset": { + "type": "number", + "description": "This option can be used to compensate for a constant error. The default is 0.0." + } + } + } + } + }, + "definitions": { + "PPS": { + "description": "Driver for the kernel PPS (pulse per second) API.", + "type": "object", + "required": [ + "name", + "device" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "enum": [ + "PPS" + ] + }, + "device": { + "type": "string", + "description": "Path to the PPS device (typically /dev/pps?).", + "pattern": "^\\/(?!\\.\\.)((?!\\/\\.\\.\\/).)+$" + }, + "clear": { + "type": "boolean", + "description": "By default, the PPS refclock uses assert events (rising edge) for synchronisation. With this option, it will use clear events (falling edge) instead." + } + } + }, + "SHM": { + "description": "NTP shared memory driver.", + "type": "object", + "required": [ + "name", + "segment" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "enum": [ + "SHM" + ] + }, + "segment": { + "type": "integer", + "description": "The number of the shared memory segment." + }, + "perm": { + "type": "string", + "pattern": "^[0-7]{4}$", + "description": "This option specifies the permissions of the shared memory segment created by chronyd. They are specified as a numeric mode. The default value is 0600 (read-write access for owner only)." + } + } + }, + "SOCK": { + "description": "Unix domain socket driver.", + "type": "object", + "required": [ + "name", + "path" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "enum": [ + "SOCK" + ] + }, + "path": { + "type": "string", + "pattern": "^\\/(?!\\.\\.)((?!\\/\\.\\.\\/).)+$", + "description": "The path to the socket." + } + } + }, + "PHC": { + "description": "Unix domain socket driver.", + "type": "object", + "required": [ + "name", + "path" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "enum": [ + "PHC" + ] + }, + "path": { + "type": "string", + "pattern": "^\\/(?!\\.\\.)((?!\\/\\.\\.\\/).)+$", + "description": "The path to the device of the PTP clock to be used as a time source." + }, + "nocrossts": { + "type": "boolean", + "description": "Disable use of precise cross timestamping." + }, + "extpps": { + "type": "boolean", + "description": "Enable a PPS mode in which the PTP clock is timestamping pulses of an external PPS signal connected to the clock." + }, + "pin": { + "type": "integer", + "description": "The index of the pin for the PPS mode. The default value is 0." + }, + "channel": { + "type": "integer", + "description": "The index of the channel for the PPS mode. The default value is 0." + }, + "clear": { + "type": "boolean", + "description": "This option enables timestamping of clear events (falling edge) instead of assert events (rising edge) in the PPS mode. This may not work with some clocks." + } + } } } }