# # Copyright (C) 2019 Red Hat, Inc. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # the GNU General Public License v.2, or (at your option) any later version. # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY expressed or implied, including the implied warranties 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. Any Red Hat trademarks that are incorporated in the # source code or documentation are not subject to the GNU General Public # License and may only be used or replicated with the express permission of # Red Hat, Inc. # import os import os.path from pyanaconda import ntp from pyanaconda.core import service, util from pyanaconda.core.configuration.anaconda import conf from pyanaconda.timezone import NTP_SERVICE from pyanaconda.anaconda_loggers import get_module_logger from pyanaconda.modules.common.errors.installation import TimezoneConfigurationError from pyanaconda.modules.common.task import Task from pyanaconda.timezone import is_valid_timezone from blivet import arch __all__ = ["ConfigureHardwareClockTask", "ConfigureNTPTask", "ConfigureTimezoneTask"] log = get_module_logger(__name__) class ConfigureHardwareClockTask(Task): """Installation task for setting the Hardware Clock from the System Clock.""" def __init__(self, is_utc): """Create a new task. :param bool is_utc: Indicate which timescale the Hardware Clock is set to """ super().__init__() self._is_utc = is_utc @property def name(self): return "Set the Hardware Clock from the System Clock" def run(self): """Perform the actual work of setting the Hardware Clock from the System Clock.""" if arch.is_s390(): log.debug("There is not Hardware Clock on s390x.") return if conf.system.can_set_hardware_clock: cmd = "hwclock" args = ["--systohc"] if self._is_utc: args.append("--utc") else: args.append("--local") util.execWithRedirect(cmd, args) class ConfigureTimezoneTask(Task): """Installation task for timezone configuration.""" def __init__(self, sysroot, timezone, is_utc): """Create a new task. :param str sysroot: a path to the root of the installed system :param str timezone: time zone to set :param bool is_utc: whether time is UTC or local """ super().__init__() self._sysroot = sysroot self._timezone = timezone self._is_utc = is_utc @property def name(self): return "Configure time zone" def run(self): """Perform the actual work of setting up timezone.""" self._correct_timezone() self._make_timezone_symlink() self._write_etc_adjtime() def _correct_timezone(self): """Ensure the timezone is valid.""" if not is_valid_timezone(self._timezone): # this should never happen, but for pity's sake log.warning("Timezone %s set in kickstart is not valid, " "falling back to default (America/New_York).", self._timezone) self._timezone = "America/New_York" def _make_timezone_symlink(self): """Create the symlink that actually defines timezone.""" # we want to create a relative symlink tz_file = "/usr/share/zoneinfo/" + self._timezone rooted_tz_file = os.path.normpath(self._sysroot + tz_file) relative_path = os.path.normpath("../" + tz_file) link_path = os.path.normpath(self._sysroot + "/etc/localtime") if not os.access(rooted_tz_file, os.R_OK): log.error("Timezone to be linked (%s) doesn't exist", rooted_tz_file) return try: # os.symlink fails if link_path exists, so try to remove it first os.remove(link_path) except OSError: pass try: os.symlink(relative_path, link_path) except OSError as oserr: log.error("Error when symlinking timezone (from %s): %s", rooted_tz_file, oserr.strerror) def _write_etc_adjtime(self): """Write /etc/adjtime contents. :raise: TimezoneConfigurationError """ if arch.is_s390(): # there is no Hardware clock on s390(x) return try: with open(os.path.normpath(self._sysroot + "/etc/adjtime"), "r") as fobj: lines = fobj.readlines() except OSError: lines = ["0.0 0 0.0\n", "0\n"] try: with open(os.path.normpath(self._sysroot + "/etc/adjtime"), "w") as fobj: fobj.write(lines[0]) fobj.write(lines[1]) if self._is_utc: fobj.write("UTC\n") else: fobj.write("LOCAL\n") except OSError as e: msg = "Error while writing /etc/adjtime file: {}".format(e.strerror) raise TimezoneConfigurationError(msg) from e class ConfigureNTPTask(Task): """Installation task for NTP configuration.""" def __init__(self, sysroot, ntp_enabled, ntp_servers): """Create a new task. :param str sysroot: a path to the root of the installed system :param bool ntp_enabled: is NTP enabled or not :param ntp_servers: list of NTP servers and pools :type ntp_servers: list of str """ super().__init__() self._sysroot = sysroot self._ntp_enabled = ntp_enabled self._ntp_servers = ntp_servers @property def name(self): return "Configure NTP" def run(self): """Perform the actual work of setting up NTP.""" self._enable_service() self._write_configuration() def _enable_service(self): """Enable or disable the chrony service.""" if not service.is_service_installed(NTP_SERVICE, root=self._sysroot): log.debug("The NTP service is not installed.") return if self._ntp_enabled: service.enable_service(NTP_SERVICE, root=self._sysroot) else: service.disable_service(NTP_SERVICE, root=self._sysroot) def _write_configuration(self): """Write the chrony configuration.""" if not (self._ntp_enabled and self._ntp_servers): log.debug("The NTP service is not enabled or configured.") return chronyd_conf_path = os.path.normpath(self._sysroot + ntp.NTP_CONFIG_FILE) if os.path.exists(chronyd_conf_path): log.debug("Modifying installed chrony configuration") try: ntp.save_servers_to_config( self._ntp_servers, conf_file_path=chronyd_conf_path ) except ntp.NTPconfigError as ntperr: log.warning("Failed to save NTP configuration: %s", ntperr) # use chrony conf file from installation environment when # chrony is not installed (chrony conf file is missing) else: log.debug("Creating chrony configuration based on the " "configuration from installation environment") try: ntp.save_servers_to_config( self._ntp_servers, conf_file_path=ntp.NTP_CONFIG_FILE, out_file_path=chronyd_conf_path ) except ntp.NTPconfigError as ntperr: log.warning("Failed to save NTP configuration without chrony package: %s", ntperr)