| /* |
| chronyd/chronyc - Programs for keeping computer clocks accurate. |
| |
| ********************************************************************** |
| * Copyright (C) Richard P. Curnow 1997-2003 |
| * Copyright (C) John G. Hasler 2009 |
| * Copyright (C) Miroslav Lichvar 2012-2017 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of version 2 of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * 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. |
| * |
| ********************************************************************** |
| |
| ======================================================================= |
| |
| The main program |
| */ |
| |
| #include "config.h" |
| |
| #include "sysincl.h" |
| |
| #include "main.h" |
| #include "sched.h" |
| #include "local.h" |
| #include "sys.h" |
| #include "ntp_io.h" |
| #include "ntp_signd.h" |
| #include "ntp_sources.h" |
| #include "ntp_core.h" |
| #include "sources.h" |
| #include "sourcestats.h" |
| #include "reference.h" |
| #include "logging.h" |
| #include "conf.h" |
| #include "cmdmon.h" |
| #include "keys.h" |
| #include "manual.h" |
| #include "rtc.h" |
| #include "refclock.h" |
| #include "clientlog.h" |
| #include "nameserv.h" |
| #include "privops.h" |
| #include "smooth.h" |
| #include "tempcomp.h" |
| #include "util.h" |
| |
| /* ================================================== */ |
| |
| /* Set when the initialisation chain has been completed. Prevents finalisation |
| * chain being run if a fatal error happened early. */ |
| |
| static int initialised = 0; |
| |
| static int exit_status = 0; |
| |
| static int reload = 0; |
| |
| static REF_Mode ref_mode = REF_ModeNormal; |
| |
| /* ================================================== */ |
| |
| static void |
| do_platform_checks(void) |
| { |
| /* Require at least 32-bit integers, two's complement representation and |
| the usual implementation of conversion of unsigned integers */ |
| assert(sizeof (int) >= 4); |
| assert(-1 == ~0); |
| assert((int32_t)4294967295U == (int32_t)-1); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| delete_pidfile(void) |
| { |
| const char *pidfile = CNF_GetPidFile(); |
| |
| if (!pidfile[0]) |
| return; |
| |
| /* Don't care if this fails, there's not a lot we can do */ |
| unlink(pidfile); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| MAI_CleanupAndExit(void) |
| { |
| if (!initialised) exit(exit_status); |
| |
| if (CNF_GetDumpDir()[0] != '\0') { |
| SRC_DumpSources(); |
| } |
| |
| /* Don't update clock when removing sources */ |
| REF_SetMode(REF_ModeIgnore); |
| |
| SMT_Finalise(); |
| TMC_Finalise(); |
| MNL_Finalise(); |
| CLG_Finalise(); |
| NSD_Finalise(); |
| NSR_Finalise(); |
| SST_Finalise(); |
| NCR_Finalise(); |
| NIO_Finalise(); |
| CAM_Finalise(); |
| KEY_Finalise(); |
| RCL_Finalise(); |
| SRC_Finalise(); |
| REF_Finalise(); |
| RTC_Finalise(); |
| SYS_Finalise(); |
| SCH_Finalise(); |
| LCL_Finalise(); |
| PRV_Finalise(); |
| |
| delete_pidfile(); |
| |
| CNF_Finalise(); |
| HSH_Finalise(); |
| LOG_Finalise(); |
| |
| exit(exit_status); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| signal_cleanup(int x) |
| { |
| if (!initialised) exit(0); |
| SCH_QuitProgram(); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| quit_timeout(void *arg) |
| { |
| /* Return with non-zero status if the clock is not synchronised */ |
| exit_status = REF_GetOurStratum() >= NTP_MAX_STRATUM; |
| SCH_QuitProgram(); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| ntp_source_resolving_end(void) |
| { |
| NSR_SetSourceResolvingEndHandler(NULL); |
| |
| if (reload) { |
| /* Note, we want reload to come well after the initialisation from |
| the real time clock - this gives us a fighting chance that the |
| system-clock scale for the reloaded samples still has a |
| semblence of validity about it. */ |
| SRC_ReloadSources(); |
| } |
| |
| SRC_RemoveDumpFiles(); |
| RTC_StartMeasurements(); |
| RCL_StartRefclocks(); |
| NSR_StartSources(); |
| NSR_AutoStartSources(); |
| |
| /* Special modes can end only when sources update their reachability. |
| Give up immediatelly if there are no active sources. */ |
| if (ref_mode != REF_ModeNormal && !SRC_ActiveSources()) { |
| REF_SetUnsynchronised(); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| post_init_ntp_hook(void *anything) |
| { |
| if (ref_mode == REF_ModeInitStepSlew) { |
| /* Remove the initstepslew sources and set normal mode */ |
| NSR_RemoveAllSources(); |
| ref_mode = REF_ModeNormal; |
| REF_SetMode(ref_mode); |
| } |
| |
| /* Close the pipe to the foreground process so it can exit */ |
| LOG_CloseParentFd(); |
| |
| CNF_AddSources(); |
| CNF_AddBroadcasts(); |
| |
| NSR_SetSourceResolvingEndHandler(ntp_source_resolving_end); |
| NSR_ResolveSources(); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| reference_mode_end(int result) |
| { |
| switch (ref_mode) { |
| case REF_ModeNormal: |
| case REF_ModeUpdateOnce: |
| case REF_ModePrintOnce: |
| exit_status = !result; |
| SCH_QuitProgram(); |
| break; |
| case REF_ModeInitStepSlew: |
| /* Switch to the normal mode, the delay is used to prevent polling |
| interval shorter than the burst interval if some configured servers |
| were used also for initstepslew */ |
| SCH_AddTimeoutByDelay(2.0, post_init_ntp_hook, NULL); |
| break; |
| default: |
| assert(0); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| post_init_rtc_hook(void *anything) |
| { |
| if (CNF_GetInitSources() > 0) { |
| CNF_AddInitSources(); |
| NSR_StartSources(); |
| assert(REF_GetMode() != REF_ModeNormal); |
| /* Wait for mode end notification */ |
| } else { |
| (post_init_ntp_hook)(NULL); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| check_pidfile(void) |
| { |
| const char *pidfile = CNF_GetPidFile(); |
| FILE *in; |
| int pid, count; |
| |
| in = fopen(pidfile, "r"); |
| if (!in) |
| return; |
| |
| count = fscanf(in, "%d", &pid); |
| fclose(in); |
| |
| if (count != 1) |
| return; |
| |
| if (getsid(pid) < 0) |
| return; |
| |
| LOG_FATAL("Another chronyd may already be running (pid=%d), check %s", |
| pid, pidfile); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| write_pidfile(void) |
| { |
| const char *pidfile = CNF_GetPidFile(); |
| FILE *out; |
| |
| if (!pidfile[0]) |
| return; |
| |
| out = fopen(pidfile, "w"); |
| if (!out) { |
| LOG_FATAL("Could not open %s : %s", pidfile, strerror(errno)); |
| } else { |
| fprintf(out, "%d\n", (int)getpid()); |
| fclose(out); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| #define DEV_NULL "/dev/null" |
| |
| static void |
| go_daemon(void) |
| { |
| int pid, fd, pipefd[2]; |
| |
| /* Create pipe which will the daemon use to notify the grandparent |
| when it's initialised or send an error message */ |
| if (pipe(pipefd)) { |
| LOG_FATAL("pipe() failed : %s", strerror(errno)); |
| } |
| |
| /* Does this preserve existing signal handlers? */ |
| pid = fork(); |
| |
| if (pid < 0) { |
| LOG_FATAL("fork() failed : %s", strerror(errno)); |
| } else if (pid > 0) { |
| /* In the 'grandparent' */ |
| char message[1024]; |
| int r; |
| |
| close(pipefd[1]); |
| r = read(pipefd[0], message, sizeof (message)); |
| if (r) { |
| if (r > 0) { |
| /* Print the error message from the child */ |
| message[sizeof (message) - 1] = '\0'; |
| fprintf(stderr, "%s\n", message); |
| } |
| exit(1); |
| } else |
| exit(0); |
| } else { |
| close(pipefd[0]); |
| |
| setsid(); |
| |
| /* Do 2nd fork, as-per recommended practice for launching daemons. */ |
| pid = fork(); |
| |
| if (pid < 0) { |
| LOG_FATAL("fork() failed : %s", strerror(errno)); |
| } else if (pid > 0) { |
| exit(0); /* In the 'parent' */ |
| } else { |
| /* In the child we want to leave running as the daemon */ |
| |
| /* Change current directory to / */ |
| if (chdir("/") < 0) { |
| LOG_FATAL("chdir() failed : %s", strerror(errno)); |
| } |
| |
| /* Don't keep stdin/out/err from before. But don't close |
| the parent pipe yet. */ |
| for (fd=0; fd<1024; fd++) { |
| if (fd != pipefd[1]) |
| close(fd); |
| } |
| |
| LOG_SetParentFd(pipefd[1]); |
| |
| /* Open /dev/null as new stdin/out/err */ |
| errno = 0; |
| if (open(DEV_NULL, O_RDONLY) != STDIN_FILENO || |
| open(DEV_NULL, O_WRONLY) != STDOUT_FILENO || |
| open(DEV_NULL, O_RDWR) != STDERR_FILENO) |
| LOG_FATAL("Could not open %s : %s", DEV_NULL, strerror(errno)); |
| } |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| print_help(const char *progname) |
| { |
| printf("Usage: %s [-4|-6] [-n|-d] [-q|-Q] [-r] [-R] [-s] [-t TIMEOUT] [-f FILE|COMMAND...]\n", |
| progname); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| print_version(void) |
| { |
| printf("chronyd (chrony) version %s (%s)\n", CHRONY_VERSION, CHRONYD_FEATURES); |
| } |
| |
| /* ================================================== */ |
| |
| static int |
| parse_int_arg(const char *arg) |
| { |
| int i; |
| |
| if (sscanf(arg, "%d", &i) != 1) |
| LOG_FATAL("Invalid argument %s", arg); |
| return i; |
| } |
| |
| /* ================================================== */ |
| |
| int main |
| (int argc, char **argv) |
| { |
| const char *conf_file = DEFAULT_CONF_FILE; |
| const char *progname = argv[0]; |
| char *user = NULL, *log_file = NULL; |
| struct passwd *pw; |
| int opt, debug = 0, nofork = 0, address_family = IPADDR_UNSPEC; |
| int do_init_rtc = 0, restarted = 0, client_only = 0, timeout = 0; |
| int scfilter_level = 0, lock_memory = 0, sched_priority = 0; |
| int clock_control = 1, system_log = 1; |
| int config_args = 0; |
| |
| do_platform_checks(); |
| |
| LOG_Initialise(); |
| |
| /* Parse (undocumented) long command-line options */ |
| for (optind = 1; optind < argc; optind++) { |
| if (!strcmp("--help", argv[optind])) { |
| print_help(progname); |
| return 0; |
| } else if (!strcmp("--version", argv[optind])) { |
| print_version(); |
| return 0; |
| } |
| } |
| |
| optind = 1; |
| |
| /* Parse short command-line options */ |
| while ((opt = getopt(argc, argv, "46df:F:hl:mnP:qQrRst:u:vx")) != -1) { |
| switch (opt) { |
| case '4': |
| case '6': |
| address_family = opt == '4' ? IPADDR_INET4 : IPADDR_INET6; |
| break; |
| case 'd': |
| debug++; |
| nofork = 1; |
| system_log = 0; |
| break; |
| case 'f': |
| conf_file = optarg; |
| break; |
| case 'F': |
| scfilter_level = parse_int_arg(optarg); |
| break; |
| case 'l': |
| log_file = optarg; |
| break; |
| case 'm': |
| lock_memory = 1; |
| break; |
| case 'n': |
| nofork = 1; |
| break; |
| case 'P': |
| sched_priority = parse_int_arg(optarg); |
| break; |
| case 'q': |
| ref_mode = REF_ModeUpdateOnce; |
| nofork = 1; |
| client_only = 0; |
| system_log = 0; |
| break; |
| case 'Q': |
| ref_mode = REF_ModePrintOnce; |
| nofork = 1; |
| client_only = 1; |
| clock_control = 0; |
| system_log = 0; |
| break; |
| case 'r': |
| reload = 1; |
| break; |
| case 'R': |
| restarted = 1; |
| break; |
| case 's': |
| do_init_rtc = 1; |
| break; |
| case 't': |
| timeout = parse_int_arg(optarg); |
| break; |
| case 'u': |
| user = optarg; |
| break; |
| case 'v': |
| print_version(); |
| return 0; |
| case 'x': |
| clock_control = 0; |
| break; |
| default: |
| print_help(progname); |
| return opt != 'h'; |
| } |
| } |
| |
| if (getuid() && !client_only) |
| LOG_FATAL("Not superuser"); |
| |
| /* Turn into a daemon */ |
| if (!nofork) { |
| go_daemon(); |
| } |
| |
| if (log_file) { |
| LOG_OpenFileLog(log_file); |
| } else if (system_log) { |
| LOG_OpenSystemLog(); |
| } |
| |
| LOG_SetDebugLevel(debug); |
| |
| LOG(LOGS_INFO, "chronyd version %s starting (%s)", CHRONY_VERSION, CHRONYD_FEATURES); |
| |
| DNS_SetAddressFamily(address_family); |
| |
| CNF_Initialise(restarted, client_only); |
| |
| /* Parse the config file or the remaining command line arguments */ |
| config_args = argc - optind; |
| if (!config_args) { |
| CNF_ReadFile(conf_file); |
| } else { |
| for (; optind < argc; optind++) |
| CNF_ParseLine(NULL, config_args + optind - argc + 1, argv[optind]); |
| } |
| |
| /* Check whether another chronyd may already be running */ |
| check_pidfile(); |
| |
| /* Write our pidfile to prevent other chronyds running */ |
| write_pidfile(); |
| |
| if (!user) |
| user = CNF_GetUser(); |
| |
| pw = getpwnam(user); |
| if (!pw) |
| LOG_FATAL("Could not get user/group ID of %s", user); |
| |
| /* Create directories for sockets, log files, and dump files */ |
| CNF_CreateDirs(pw->pw_uid, pw->pw_gid); |
| |
| PRV_Initialise(); |
| LCL_Initialise(); |
| SCH_Initialise(); |
| SYS_Initialise(clock_control); |
| RTC_Initialise(do_init_rtc); |
| SRC_Initialise(); |
| RCL_Initialise(); |
| KEY_Initialise(); |
| |
| /* Open privileged ports before dropping root */ |
| CAM_Initialise(address_family); |
| NIO_Initialise(address_family); |
| NCR_Initialise(); |
| CNF_SetupAccessRestrictions(); |
| |
| /* Command-line switch must have priority */ |
| if (!sched_priority) { |
| sched_priority = CNF_GetSchedPriority(); |
| } |
| if (sched_priority) { |
| SYS_SetScheduler(sched_priority); |
| } |
| |
| if (lock_memory || CNF_GetLockMemory()) { |
| SYS_LockMemory(); |
| } |
| |
| /* Drop root privileges if the specified user has a non-zero UID */ |
| if (!geteuid() && (pw->pw_uid || pw->pw_gid)) |
| SYS_DropRoot(pw->pw_uid, pw->pw_gid); |
| |
| REF_Initialise(); |
| SST_Initialise(); |
| NSR_Initialise(); |
| NSD_Initialise(); |
| CLG_Initialise(); |
| MNL_Initialise(); |
| TMC_Initialise(); |
| SMT_Initialise(); |
| |
| /* From now on, it is safe to do finalisation on exit */ |
| initialised = 1; |
| |
| UTI_SetQuitSignalsHandler(signal_cleanup); |
| |
| CAM_OpenUnixSocket(); |
| |
| if (scfilter_level) |
| SYS_EnableSystemCallFilter(scfilter_level); |
| |
| if (ref_mode == REF_ModeNormal && CNF_GetInitSources() > 0) { |
| ref_mode = REF_ModeInitStepSlew; |
| } |
| |
| REF_SetModeEndHandler(reference_mode_end); |
| REF_SetMode(ref_mode); |
| |
| if (timeout > 0) |
| SCH_AddTimeoutByDelay(timeout, quit_timeout, NULL); |
| |
| if (do_init_rtc) { |
| RTC_TimeInit(post_init_rtc_hook, NULL); |
| } else { |
| post_init_rtc_hook(NULL); |
| } |
| |
| /* The program normally runs under control of the main loop in |
| the scheduler. */ |
| SCH_MainLoop(); |
| |
| LOG(LOGS_INFO, "chronyd exiting"); |
| |
| MAI_CleanupAndExit(); |
| |
| return 0; |
| } |
| |
| /* ================================================== */ |