github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/configstate/configcore/swap.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 "io/ioutil" 25 "os" 26 "path/filepath" 27 "time" 28 29 "github.com/mvo5/goconfigparser" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/gadget/quantity" 33 "github.com/snapcore/snapd/osutil" 34 "github.com/snapcore/snapd/overlord/configstate/config" 35 "github.com/snapcore/snapd/sysconfig" 36 "github.com/snapcore/snapd/systemd" 37 ) 38 39 func init() { 40 supportedConfigurations["core.swap.size"] = true 41 } 42 43 func validateSystemSwapConfiguration(tr config.ConfGetter) error { 44 output, err := coreCfg(tr, "swap.size") 45 if err != nil { 46 return err 47 } 48 49 if output == "" { 50 return nil 51 } 52 53 // valid option for swap size is any integer multiple of a megabyte that is 54 // larger than or equal to 1 MB, or 0 for no swap enabled. 55 _, err = parseAndValidateSwapSize(output) 56 return err 57 } 58 59 func parseAndValidateSwapSize(szString string) (quantity.Size, error) { 60 sz, err := quantity.ParseSize(szString) 61 if err != nil { 62 return 0, err 63 } 64 65 switch { 66 case sz < 0: 67 // negative doesn't make sense 68 return 0, fmt.Errorf("swap size setting must be positive size in megabytes") 69 case sz > 0 && sz < quantity.SizeMiB: 70 // too small 71 return 0, fmt.Errorf("swap size setting must be at least one megabyte") 72 case sz%quantity.SizeMiB != 0: 73 // must be even number of megabytes 74 return 0, fmt.Errorf("swap size setting must be an integer number of megabytes") 75 } 76 return sz, nil 77 } 78 79 func handlesystemSwapConfiguration(_ sysconfig.Device, tr config.ConfGetter, opts *fsOnlyContext) error { 80 var pristineSwapSize, newSwapSize string 81 if err := tr.GetPristine("core", "swap.size", &pristineSwapSize); err != nil && !config.IsNoOption(err) { 82 return err 83 } 84 if err := tr.Get("core", "swap.size", &newSwapSize); err != nil && !config.IsNoOption(err) { 85 return err 86 } 87 if pristineSwapSize == newSwapSize { 88 return nil 89 } 90 91 // if it's unset, then treat it as if the size is "0" to not use swap by 92 // default 93 if newSwapSize == "" { 94 newSwapSize = "0" 95 } 96 97 szBytes, err := parseAndValidateSwapSize(newSwapSize) 98 if err != nil { 99 return err 100 } 101 102 rootDir := dirs.GlobalRootDir 103 if opts != nil { 104 rootDir = opts.RootDir 105 } 106 107 swapConfigPath := filepath.Join(rootDir, "/etc/default/swapfile") 108 109 // TODO: also support writing/setting the location of the swap file setting? 110 111 // default location of the swapfile in case we can't determine the location 112 // from the config file 113 location := "/var/tmp/swapfile.swp" 114 if osutil.FileExists(swapConfigPath) { 115 // then get values from the config file 116 // read the existing file to get the location setting 117 cfg := goconfigparser.New() 118 cfg.AllowNoSectionHeader = true 119 120 if err := cfg.ReadFile(swapConfigPath); err != nil { 121 return err 122 } 123 124 location, err = cfg.Get("", "FILE") 125 if err != nil { 126 return err 127 } 128 } 129 130 // ensure the directory exists 131 if err := os.MkdirAll(filepath.Dir(swapConfigPath), 0755); err != nil { 132 return err 133 } 134 135 // the size of swap needs to be specified in Megabytes, while quantity.Size 136 // is a uint64 of bytes 137 fileContent := fmt.Sprintf("FILE=%s\nSIZE=%d\n", location, szBytes/quantity.SizeMiB) 138 139 // write the swap config file 140 if err := ioutil.WriteFile(swapConfigPath, []byte(fileContent), 0644); err != nil { 141 return err 142 } 143 144 if opts == nil { 145 // if we are not doing this filesystem only, then we need to also 146 // restart the swap service 147 sysd := systemd.NewUnderRoot(dirs.GlobalRootDir, systemd.SystemMode, &backlightSysdLogger{}) 148 149 // TODO: what's an appropriate amount of time to wait here? 150 if err := sysd.Restart("swapfile.service", 60*time.Second); err != nil { 151 return err 152 } 153 } 154 155 return nil 156 }