github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/gadget/install/install.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 // +build !nosecboot 3 4 /* 5 * Copyright (C) 2019-2020 Canonical Ltd 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 3 as 9 * published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 package install 22 23 import ( 24 "fmt" 25 "os" 26 "path/filepath" 27 28 "github.com/snapcore/snapd/boot" 29 "github.com/snapcore/snapd/gadget" 30 "github.com/snapcore/snapd/secboot" 31 ) 32 33 const ( 34 ubuntuDataLabel = "ubuntu-data" 35 ) 36 37 func deviceFromRole(lv *gadget.LaidOutVolume, role string) (device string, err error) { 38 for _, vs := range lv.LaidOutStructure { 39 // XXX: this part of the finding maybe should be a 40 // method on gadget.*Volume 41 if vs.Role == role { 42 device, err = gadget.FindDeviceForStructure(&vs) 43 if err != nil { 44 return "", fmt.Errorf("cannot find device for role %q: %v", role, err) 45 } 46 return gadget.ParentDiskFromPartition(device) 47 } 48 } 49 return "", fmt.Errorf("cannot find role %s in gadget", role) 50 } 51 52 // Run bootstraps the partitions of a device, by either creating 53 // missing ones or recreating installed ones. 54 func Run(gadgetRoot, device string, options Options, observer SystemInstallObserver) error { 55 if gadgetRoot == "" { 56 return fmt.Errorf("cannot use empty gadget root directory") 57 } 58 59 lv, err := gadget.PositionedVolumeFromGadget(gadgetRoot) 60 if err != nil { 61 return fmt.Errorf("cannot layout the volume: %v", err) 62 } 63 64 // XXX: the only situation where auto-detect is not desired is 65 // in (spread) testing - consider to remove forcing a device 66 // 67 // auto-detect device if no device is forced 68 if device == "" { 69 device, err = deviceFromRole(lv, gadget.SystemSeed) 70 if err != nil { 71 return fmt.Errorf("cannot find device to create partitions on: %v", err) 72 } 73 } 74 75 diskLayout, err := gadget.OnDiskVolumeFromDevice(device) 76 if err != nil { 77 return fmt.Errorf("cannot read %v partitions: %v", device, err) 78 } 79 80 // check if the current partition table is compatible with the gadget, 81 // ignoring partitions added by the installer (will be removed later) 82 if err := ensureLayoutCompatibility(lv, diskLayout); err != nil { 83 return fmt.Errorf("gadget and %v partition table not compatible: %v", device, err) 84 } 85 86 // remove partitions added during a previous install attempt 87 if err := removeCreatedPartitions(diskLayout); err != nil { 88 return fmt.Errorf("cannot remove partitions from previous install: %v", err) 89 } 90 // at this point we removed any existing partition, nuke any 91 // of the existing sealed key files placed outside of the 92 // encrypted partitions (LP: #1879338) 93 sealedKeyFiles, _ := filepath.Glob(filepath.Join(boot.InitramfsEncryptionKeyDir, "*.sealed-key")) 94 for _, keyFile := range sealedKeyFiles { 95 if err := os.Remove(keyFile); err != nil && !os.IsNotExist(err) { 96 return fmt.Errorf("cannot cleanup obsolete key file: %v", keyFile) 97 } 98 } 99 100 created, err := createMissingPartitions(diskLayout, lv) 101 if err != nil { 102 return fmt.Errorf("cannot create the partitions: %v", err) 103 } 104 105 // We're currently generating a single encryption key, this may change later 106 // if we create multiple encrypted partitions. 107 var key secboot.EncryptionKey 108 var rkey secboot.RecoveryKey 109 110 if options.Encrypt { 111 key, err = secboot.NewEncryptionKey() 112 if err != nil { 113 return fmt.Errorf("cannot create encryption key: %v", err) 114 } 115 116 rkey, err = secboot.NewRecoveryKey() 117 if err != nil { 118 return fmt.Errorf("cannot create recovery key: %v", err) 119 } 120 } 121 122 for _, part := range created { 123 if options.Encrypt && part.Role == gadget.SystemData { 124 dataPart, err := newEncryptedDevice(&part, key, ubuntuDataLabel) 125 if err != nil { 126 return err 127 } 128 129 if err := dataPart.AddRecoveryKey(key, rkey); err != nil { 130 return err 131 } 132 133 // update the encrypted device node 134 part.Node = dataPart.Node 135 } 136 137 if err := makeFilesystem(&part); err != nil { 138 return err 139 } 140 141 if err := writeContent(&part, gadgetRoot, observer); err != nil { 142 return err 143 } 144 145 if options.Mount && part.Label != "" && part.HasFilesystem() { 146 if err := mountFilesystem(&part, boot.InitramfsRunMntDir); err != nil { 147 return err 148 } 149 } 150 } 151 152 if !options.Encrypt { 153 return nil 154 } 155 156 // ensure directories 157 for _, p := range []string{boot.InitramfsEncryptionKeyDir, boot.InstallHostFDEDataDir} { 158 if err := os.MkdirAll(p, 0755); err != nil { 159 return err 160 } 161 } 162 163 // Write the recovery key 164 recoveryKeyFile := filepath.Join(boot.InstallHostFDEDataDir, "recovery.key") 165 if err := rkey.Save(recoveryKeyFile); err != nil { 166 return fmt.Errorf("cannot store recovery key: %v", err) 167 } 168 169 if observer != nil { 170 observer.ChosenEncryptionKey(key) 171 } 172 173 return nil 174 } 175 176 func ensureLayoutCompatibility(gadgetLayout *gadget.LaidOutVolume, diskLayout *gadget.OnDiskVolume) error { 177 eq := func(ds gadget.OnDiskStructure, gs gadget.LaidOutStructure) bool { 178 dv := ds.VolumeStructure 179 gv := gs.VolumeStructure 180 nameMatch := gv.Name == dv.Name 181 if gadgetLayout.Schema == "mbr" { 182 // partitions have no names in MBR 183 nameMatch = true 184 } 185 // Previous installation may have failed before filesystem creation or partition may be encrypted 186 check := nameMatch && ds.StartOffset == gs.StartOffset && (ds.CreatedDuringInstall || dv.Filesystem == gv.Filesystem) 187 if gv.Role == gadget.SystemData { 188 // system-data may have been expanded 189 return check && dv.Size >= gv.Size 190 } 191 return check && dv.Size == gv.Size 192 } 193 contains := func(haystack []gadget.LaidOutStructure, needle gadget.OnDiskStructure) bool { 194 for _, h := range haystack { 195 if eq(needle, h) { 196 return true 197 } 198 } 199 return false 200 } 201 202 if gadgetLayout.Size > diskLayout.Size { 203 return fmt.Errorf("device %v (%s) is too small to fit the requested layout (%s)", diskLayout.Device, 204 diskLayout.Size.IECString(), gadgetLayout.Size.IECString()) 205 } 206 207 // Check if top level properties match 208 if !isCompatibleSchema(gadgetLayout.Volume.Schema, diskLayout.Schema) { 209 return fmt.Errorf("disk partitioning schema %q doesn't match gadget schema %q", diskLayout.Schema, gadgetLayout.Volume.Schema) 210 } 211 if gadgetLayout.Volume.ID != "" && gadgetLayout.Volume.ID != diskLayout.ID { 212 return fmt.Errorf("disk ID %q doesn't match gadget volume ID %q", diskLayout.ID, gadgetLayout.Volume.ID) 213 } 214 215 // Check if all existing device partitions are also in gadget 216 for _, ds := range diskLayout.Structure { 217 if !contains(gadgetLayout.LaidOutStructure, ds) { 218 return fmt.Errorf("cannot find disk partition %s (starting at %d) in gadget", ds.Node, ds.StartOffset) 219 } 220 } 221 222 return nil 223 } 224 225 func isCompatibleSchema(gadgetSchema, diskSchema string) bool { 226 switch gadgetSchema { 227 // XXX: "mbr,gpt" is currently unsupported 228 case "", "gpt": 229 return diskSchema == "gpt" 230 case "mbr": 231 return diskSchema == "dos" 232 default: 233 return false 234 } 235 }