github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 29 "github.com/snapcore/snapd/osutil" 30 "github.com/snapcore/snapd/overlord/configstate/config" 31 "github.com/snapcore/snapd/sysconfig" 32 ) 33 34 func init() { 35 // add supported configuration of this module 36 supportedConfigurations["core.system.timezone"] = true 37 } 38 39 var validTimezone = regexp.MustCompile(`^[a-zA-Z0-9+_-]+(/[a-zA-Z0-9+_-]+)?(/[a-zA-Z0-9+_-]+)?$`).MatchString 40 41 func validateTimezoneSettings(tr config.ConfGetter) error { 42 timezone, err := coreCfg(tr, "system.timezone") 43 if err != nil { 44 return err 45 } 46 if timezone == "" { 47 return nil 48 } 49 if !validTimezone(timezone) { 50 return fmt.Errorf("cannot set timezone %q: name not valid", timezone) 51 } 52 53 return nil 54 } 55 56 func handleTimezoneConfiguration(_ sysconfig.Device, tr config.ConfGetter, opts *fsOnlyContext) error { 57 // TODO: convert to "virtual" configuration nodes once we have support 58 // for this. The current code is not ideal because if one calls 59 // `snap get system system.hostname` the answer can be "" 60 // when not set via snap set. 61 // 62 // It will also override any hostname on the next `snap set` run 63 // that was written not using `snap set system system.hostname`. 64 timezone, err := coreCfg(tr, "system.timezone") 65 if err != nil { 66 return nil 67 } 68 // nothing to do 69 if timezone == "" { 70 return nil 71 } 72 // runtime system 73 if opts == nil { 74 output, err := exec.Command("timedatectl", "set-timezone", timezone).CombinedOutput() 75 if err != nil { 76 return fmt.Errorf("cannot set timezone: %v", osutil.OutputErr(output, err)) 77 } 78 } else { 79 // On the UC16/UC18/UC20 images the file /etc/hostname is a 80 // symlink to /etc/writable/hostname. The /etc/hostname is 81 // not part of the "writable-path" so we must set the file 82 // in /etc/writable here for this to work. 83 localtimePath := filepath.Join(opts.RootDir, "/etc/writable/localtime") 84 if err := os.MkdirAll(filepath.Dir(localtimePath), 0755); err != nil { 85 return err 86 } 87 if err := os.Symlink(filepath.Join("/usr/share/zoneinfo", timezone), localtimePath); err != nil { 88 return err 89 } 90 timezonePath := filepath.Join(opts.RootDir, "/etc/writable/timezone") 91 if err := osutil.AtomicWriteFile(timezonePath, []byte(timezone+"\n"), 0644, 0); err != nil { 92 return fmt.Errorf("cannot write timezone: %v", err) 93 } 94 } 95 96 return nil 97 }