github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/configstate/configcore/timezone.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package configcore 21 22 import ( 23 "fmt" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "regexp" 28 "strings" 29 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/osutil" 32 "github.com/snapcore/snapd/overlord/configstate/config" 33 "github.com/snapcore/snapd/sysconfig" 34 ) 35 36 func init() { 37 // add supported configuration of this module 38 supportedConfigurations["core.system.timezone"] = true 39 // and register it as a external config 40 config.RegisterExternalConfig("core", "system.timezone", getTimezoneFromSystemVC) 41 } 42 43 var validTimezone = regexp.MustCompile(`^[a-zA-Z0-9+_-]+(/[a-zA-Z0-9+_-]+)?(/[a-zA-Z0-9+_-]+)?$`).MatchString 44 45 func validateTimezoneSettings(tr config.ConfGetter) error { 46 timezone, err := coreCfg(tr, "system.timezone") 47 if err != nil { 48 return err 49 } 50 if timezone == "" { 51 return nil 52 } 53 if !validTimezone(timezone) { 54 return fmt.Errorf("cannot set timezone %q: name not valid", timezone) 55 } 56 57 return nil 58 } 59 60 func handleTimezoneConfiguration(_ sysconfig.Device, tr config.ConfGetter, opts *fsOnlyContext) error { 61 timezone, err := coreCfg(tr, "system.timezone") 62 if err != nil { 63 return err 64 } 65 // nothing to do 66 if timezone == "" { 67 return nil 68 } 69 // runtime system 70 if opts == nil { 71 // see if anything has changed 72 currentTimezone, err := getTimezoneFromSystem() 73 if err != nil { 74 return err 75 } 76 if timezone == currentTimezone { 77 return nil 78 } 79 80 output, err := exec.Command("timedatectl", "set-timezone", timezone).CombinedOutput() 81 if err != nil { 82 return fmt.Errorf("cannot set timezone: %v", osutil.OutputErr(output, err)) 83 } 84 } else { 85 // On the UC16/UC18/UC20 images the file /etc/hostname is a 86 // symlink to /etc/writable/hostname. The /etc/hostname is 87 // not part of the "writable-path" so we must set the file 88 // in /etc/writable here for this to work. 89 localtimePath := filepath.Join(opts.RootDir, "/etc/writable/localtime") 90 if err := os.MkdirAll(filepath.Dir(localtimePath), 0755); err != nil { 91 return err 92 } 93 if err := os.Symlink(filepath.Join("/usr/share/zoneinfo", timezone), localtimePath); err != nil { 94 return err 95 } 96 timezonePath := filepath.Join(opts.RootDir, "/etc/writable/timezone") 97 if err := osutil.AtomicWriteFile(timezonePath, []byte(timezone+"\n"), 0644, 0); err != nil { 98 return fmt.Errorf("cannot write timezone: %v", err) 99 } 100 } 101 102 return nil 103 } 104 105 func getTimezoneFromSystemVC(key string) (interface{}, error) { 106 return getTimezoneFromSystem() 107 } 108 109 func getTimezoneFromSystem() (string, error) { 110 // We cannot use "timedatectl show" here because it is only 111 // available on UC20. 112 // 113 // Note that this code only runs on UbuntuCore systems which all 114 // have /etc/writable/localtime 115 link, err := os.Readlink(filepath.Join(dirs.GlobalRootDir, "/etc/writable/localtime")) 116 // see localtime(5) 117 // "If /etc/localtime is missing, the default "UTC" timezone is used." 118 if os.IsNotExist(err) { 119 return "UTC", nil 120 } 121 if err != nil { 122 return "", fmt.Errorf("cannot get timezone: %v", err) 123 } 124 val := strings.TrimPrefix(link, "/usr/share/zoneinfo/") 125 return val, nil 126 }