github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/ntpdate/ntpdate.go (about)

     1  // Copyright 2021 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ntpdate
     6  
     7  import (
     8  	"bufio"
     9  	"fmt"
    10  	"os"
    11  	"strings"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/beevik/ntp"
    16  
    17  	"github.com/mvdan/u-root-coreutils/pkg/rtc"
    18  )
    19  
    20  const DefaultNTPConfig = "/etc/ntp.conf"
    21  
    22  var Debug = func(string, ...interface{}) {}
    23  
    24  func parseServers(r *bufio.Reader) []string {
    25  	var uri []string
    26  	var l string
    27  	var err error
    28  
    29  	Debug("Reading config file")
    30  	for err == nil {
    31  		// This handles the case where the last line doesn't end in \n
    32  		l, err = r.ReadString('\n')
    33  		Debug("%v", l)
    34  		if w := strings.Fields(l); len(w) > 1 && w[0] == "server" {
    35  			// We look only for the server lines, we ignore options like iburst
    36  			// TODO(ganshun): figure out what options we want to support.
    37  			uri = append(uri, w[1])
    38  		}
    39  	}
    40  
    41  	return uri
    42  }
    43  
    44  func getTime(servers []string) (time.Time, string, error) {
    45  	for _, s := range servers {
    46  		Debug("Getting time from %v", s)
    47  		t, err := ntp.Time(s)
    48  		if err == nil {
    49  			// Right now we return on the first valid time.
    50  			// We can implement better heuristics here.
    51  			Debug("Got time %v", t)
    52  			return t, s, nil
    53  		}
    54  		Debug("Error getting time from %s: %v", s, err)
    55  	}
    56  
    57  	return time.Time{}, "", fmt.Errorf("unable to get any time from servers %v", servers)
    58  }
    59  
    60  // SetTime sets system and optionally RTC time from NTP servers specified in sersers or the config file.
    61  // If successful, returns the server used to set the time and the offset, in seconds.
    62  func SetTime(servers []string, config string, fallback string, setRTC bool) (string, float64, error) {
    63  	return setTime(servers, config, fallback, setRTC, &realGetterSetter{})
    64  }
    65  
    66  type timeGetterSetter interface {
    67  	GetTime(servers []string) (time.Time, string, error)
    68  	SetSystemTime(time.Time) error
    69  	SetRTCTime(time.Time) error
    70  }
    71  
    72  type realGetterSetter struct{}
    73  
    74  func (*realGetterSetter) GetTime(servers []string) (time.Time, string, error) {
    75  	return getTime(servers)
    76  }
    77  
    78  func (*realGetterSetter) SetSystemTime(t time.Time) error {
    79  	tv := syscall.NsecToTimeval(t.UnixNano())
    80  	return syscall.Settimeofday(&tv)
    81  }
    82  
    83  func (*realGetterSetter) SetRTCTime(t time.Time) error {
    84  	r, err := rtc.OpenRTC()
    85  	if err != nil {
    86  		return fmt.Errorf("unable to open RTC: %w", err)
    87  	}
    88  	defer r.Close()
    89  	return r.Set(t)
    90  }
    91  
    92  func setTime(servers []string, config string, fallback string, setRTC bool, gs timeGetterSetter) (string, float64, error) {
    93  	servers = servers[:]
    94  
    95  	if config != "" {
    96  		Debug("Reading NTP servers from config file: %v", config)
    97  		f, err := os.Open(config)
    98  		if err == nil {
    99  			defer f.Close()
   100  			configServers := parseServers(bufio.NewReader(f))
   101  			Debug("Found %v servers", len(configServers))
   102  			servers = append(servers, configServers...)
   103  		} else {
   104  			Debug("Unable to open config file: %v", err)
   105  		}
   106  	}
   107  
   108  	if len(servers) == 0 && len(fallback) != 0 {
   109  		Debug("No servers provided, falling back to %v", fallback)
   110  		servers = append(servers, fallback)
   111  	}
   112  
   113  	if len(servers) == 0 {
   114  		return "", 0, fmt.Errorf("no servers")
   115  	}
   116  
   117  	t, server, err := gs.GetTime(servers)
   118  	if err != nil {
   119  		return "", 0, fmt.Errorf("unable to get time: %w", err)
   120  	}
   121  
   122  	offset := time.Until(t).Seconds()
   123  
   124  	if err = gs.SetSystemTime(t); err != nil {
   125  		return "", 0, fmt.Errorf("unable to set system time: %w", err)
   126  	}
   127  	if setRTC {
   128  		Debug("Setting RTC time...")
   129  		if err = gs.SetRTCTime(t); err != nil {
   130  			return "", 0, fmt.Errorf("unable to set RTC time: %w", err)
   131  		}
   132  	}
   133  
   134  	return server, offset, nil
   135  }