github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/osutil/disks/disks_test.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 disks_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 28 . "gopkg.in/check.v1" 29 30 "golang.org/x/xerrors" 31 32 "github.com/snapcore/snapd/dirs" 33 "github.com/snapcore/snapd/osutil" 34 "github.com/snapcore/snapd/osutil/disks" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 type diskSuite struct { 39 testutil.BaseTest 40 } 41 42 var _ = Suite(&diskSuite{}) 43 44 func (s *diskSuite) SetUpTest(c *C) { 45 dirs.SetRootDir(c.MkDir()) 46 } 47 48 func (s *diskSuite) TestDiskFromMountPointUnhappyMissingMountpoint(c *C) { 49 // no mount points 50 restore := osutil.MockMountInfo(``) 51 defer restore() 52 53 _, err := disks.DiskFromMountPoint("/run/mnt/blah", nil) 54 c.Assert(err, ErrorMatches, "cannot find mountpoint \"/run/mnt/blah\"") 55 } 56 57 func (s *diskSuite) TestDiskFromMountPointUnhappyMissingUdevProps(c *C) { 58 restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda4 rw 59 `) 60 defer restore() 61 62 restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) { 63 c.Assert(dev, Equals, "/dev/vda4") 64 return map[string]string{ 65 "prop": "hello", 66 }, nil 67 }) 68 defer restore() 69 70 _, err := disks.DiskFromMountPoint("/run/mnt/point", nil) 71 c.Assert(err, ErrorMatches, "cannot find disk for partition /dev/vda4, incomplete udev output") 72 } 73 74 func (s *diskSuite) TestDiskFromMountPointUnhappyBadUdevPropsMountpointPartition(c *C) { 75 restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda4 rw 76 `) 77 defer restore() 78 79 restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) { 80 c.Assert(dev, Equals, "/dev/vda4") 81 return map[string]string{ 82 "ID_PART_ENTRY_DISK": "not-a-number", 83 }, nil 84 }) 85 defer restore() 86 87 _, err := disks.DiskFromMountPoint("/run/mnt/point", nil) 88 c.Assert(err, ErrorMatches, `cannot find disk for partition /dev/vda4, bad udev output: invalid device number format: \(expected <int>:<int>\)`) 89 } 90 91 func (s *diskSuite) TestDiskFromMountPointUnhappyIsDecryptedDeviceNotDiskDevice(c *C) { 92 restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda4 rw 93 `) 94 defer restore() 95 96 restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) { 97 switch dev { 98 case "/dev/vda4": 99 return map[string]string{ 100 "ID_PART_ENTRY_DISK": "42:0", 101 // DEVTYPE == partition is unexpected for this, so this makes 102 // DiskFromMountPoint fail, as decrypted devices should not be 103 // direct partitions, they should be mapper device volumes/disks 104 "DEVTYPE": "partition", 105 }, nil 106 default: 107 c.Logf("unexpected udev device properties requested: %s", dev) 108 c.Fail() 109 return nil, fmt.Errorf("unexpected udev device") 110 } 111 }) 112 defer restore() 113 114 opts := &disks.Options{IsDecryptedDevice: true} 115 _, err := disks.DiskFromMountPoint("/run/mnt/point", opts) 116 c.Assert(err, ErrorMatches, `mountpoint source /dev/vda4 is not a decrypted device: devtype is not disk \(is partition\)`) 117 } 118 119 func (s *diskSuite) TestDiskFromMountPointUnhappyIsDecryptedDeviceNoSysfs(c *C) { 120 restore := osutil.MockMountInfo(`130 30 252:0 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/mapper/something rw 121 `) 122 defer restore() 123 124 restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) { 125 switch dev { 126 case "/dev/mapper/something": 127 return map[string]string{ 128 "DEVTYPE": "disk", 129 }, nil 130 default: 131 c.Logf("unexpected udev device properties requested: %s", dev) 132 c.Fail() 133 return nil, fmt.Errorf("unexpected udev device") 134 135 } 136 }) 137 defer restore() 138 139 // no sysfs files mocking 140 141 opts := &disks.Options{IsDecryptedDevice: true} 142 _, err := disks.DiskFromMountPoint("/run/mnt/point", opts) 143 c.Assert(err, ErrorMatches, `mountpoint source /dev/mapper/something is not a decrypted device: could not read device mapper metadata: open /sys/dev/block/252:0/dm/uuid: no such file or directory`) 144 } 145 146 func (s *diskSuite) TestDiskFromMountPointHappyNoPartitions(c *C) { 147 restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda4 rw 148 `) 149 defer restore() 150 151 // mock just the partition's disk major minor in udev, but no actual 152 // partitions 153 restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) { 154 switch dev { 155 case "/dev/block/42:1", "/dev/vda4": 156 return map[string]string{ 157 "ID_PART_ENTRY_DISK": "42:0", 158 }, nil 159 default: 160 c.Logf("unexpected udev device properties requested: %s", dev) 161 c.Fail() 162 return nil, fmt.Errorf("unexpected udev device") 163 164 } 165 }) 166 defer restore() 167 168 disk, err := disks.DiskFromMountPoint("/run/mnt/point", nil) 169 c.Assert(err, IsNil) 170 c.Assert(disk.Dev(), Equals, "42:0") 171 c.Assert(disk.HasPartitions(), Equals, true) 172 // trying to search for any labels though will fail 173 _, err = disk.FindMatchingPartitionUUID("ubuntu-boot") 174 c.Assert(err, ErrorMatches, "no partitions found for disk 42:0") 175 } 176 177 func (s *diskSuite) TestDiskFromMountPointHappyOnePartition(c *C) { 178 restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda1 rw 179 `) 180 defer restore() 181 182 restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) { 183 switch dev { 184 case "/dev/block/42:1", "/dev/vda1": 185 return map[string]string{ 186 "ID_PART_ENTRY_DISK": "42:0", 187 "DEVTYPE": "partition", 188 "ID_FS_LABEL_ENC": "ubuntu-seed", 189 "ID_PART_ENTRY_UUID": "ubuntu-seed-partuuid", 190 }, nil 191 case "/dev/block/42:2": 192 return nil, fmt.Errorf("Unknown device 42:2") 193 default: 194 c.Logf("unexpected udev device properties requested: %s", dev) 195 c.Fail() 196 return nil, fmt.Errorf("unexpected udev device") 197 198 } 199 }) 200 defer restore() 201 202 d, err := disks.DiskFromMountPoint("/run/mnt/point", nil) 203 c.Assert(err, IsNil) 204 c.Assert(d.Dev(), Equals, "42:0") 205 c.Assert(d.HasPartitions(), Equals, true) 206 207 label, err := d.FindMatchingPartitionUUID("ubuntu-seed") 208 c.Assert(err, IsNil) 209 c.Assert(label, Equals, "ubuntu-seed-partuuid") 210 } 211 212 func (s *diskSuite) TestDiskFromMountPointHappy(c *C) { 213 restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda1 rw 214 `) 215 defer restore() 216 217 udevadmCmd := testutil.MockCommand(c, "udevadm", ` 218 if [ "$*" = "info --query property --name /dev/vda1" ]; then 219 echo "ID_PART_ENTRY_DISK=42:0" 220 else 221 echo "unexpected arguments" 222 exit 1 223 fi 224 `) 225 226 d, err := disks.DiskFromMountPoint("/run/mnt/point", nil) 227 c.Assert(err, IsNil) 228 c.Assert(d.Dev(), Equals, "42:0") 229 c.Assert(d.HasPartitions(), Equals, true) 230 231 c.Assert(udevadmCmd.Calls(), DeepEquals, [][]string{ 232 {"udevadm", "info", "--query", "property", "--name", "/dev/vda1"}, 233 }) 234 } 235 236 func (s *diskSuite) TestDiskFromMountPointVolumeHappy(c *C) { 237 restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/mapper/something rw 238 `) 239 defer restore() 240 241 udevadmCmd := testutil.MockCommand(c, "udevadm", ` 242 if [ "$*" = "info --query property --name /dev/mapper/something" ]; then 243 # not a partition, so no ID_PART_ENTRY_DISK, but we will have DEVTYPE=disk 244 echo "DEVTYPE=disk" 245 else 246 echo "unexpected arguments" 247 exit 1 248 fi 249 `) 250 251 d, err := disks.DiskFromMountPoint("/run/mnt/point", nil) 252 c.Assert(err, IsNil) 253 c.Assert(d.Dev(), Equals, "42:1") 254 c.Assert(d.HasPartitions(), Equals, false) 255 256 c.Assert(udevadmCmd.Calls(), DeepEquals, [][]string{ 257 {"udevadm", "info", "--query", "property", "--name", "/dev/mapper/something"}, 258 }) 259 } 260 261 func (s *diskSuite) TestDiskFromMountPointIsDecryptedDeviceVolumeHappy(c *C) { 262 restore := osutil.MockMountInfo(`130 30 242:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/mapper/something rw 263 `) 264 defer restore() 265 266 restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) { 267 switch dev { 268 case "/dev/mapper/something": 269 return map[string]string{ 270 "DEVTYPE": "disk", 271 }, nil 272 case "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304": 273 return map[string]string{ 274 "DEVTYPE": "disk", 275 }, nil 276 default: 277 c.Logf("unexpected udev device properties requested: %s", dev) 278 c.Fail() 279 return nil, fmt.Errorf("unexpected udev device") 280 281 } 282 }) 283 defer restore() 284 285 // mock the /sys/dev/block dir 286 devBlockDir := filepath.Join(dirs.SysfsDir, "dev", "block") 287 restore = disks.MockDevBlockDir(devBlockDir) 288 defer restore() 289 290 // mock the sysfs dm uuid and name files 291 dmDir := filepath.Join(devBlockDir, "242:1", "dm") 292 err := os.MkdirAll(dmDir, 0755) 293 c.Assert(err, IsNil) 294 295 b := []byte("something") 296 err = ioutil.WriteFile(filepath.Join(dmDir, "name"), b, 0644) 297 c.Assert(err, IsNil) 298 299 b = []byte("CRYPT-LUKS2-5a522809c87e4dfa81a88dc5667d1304-something") 300 err = ioutil.WriteFile(filepath.Join(dmDir, "uuid"), b, 0644) 301 c.Assert(err, IsNil) 302 303 opts := &disks.Options{IsDecryptedDevice: true} 304 d, err := disks.DiskFromMountPoint("/run/mnt/point", opts) 305 c.Assert(err, IsNil) 306 c.Assert(d.Dev(), Equals, "242:1") 307 c.Assert(d.HasPartitions(), Equals, false) 308 } 309 310 func (s *diskSuite) TestDiskFromMountPointNotDiskUnsupported(c *C) { 311 restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/not-a-disk rw 312 `) 313 defer restore() 314 315 udevadmCmd := testutil.MockCommand(c, "udevadm", ` 316 if [ "$*" = "info --query property --name /dev/not-a-disk" ]; then 317 echo "DEVTYPE=not-a-disk" 318 else 319 echo "unexpected arguments" 320 exit 1 321 fi 322 `) 323 324 _, err := disks.DiskFromMountPoint("/run/mnt/point", nil) 325 c.Assert(err, ErrorMatches, "unsupported DEVTYPE \"not-a-disk\" for mount point source /dev/not-a-disk") 326 327 c.Assert(udevadmCmd.Calls(), DeepEquals, [][]string{ 328 {"udevadm", "info", "--query", "property", "--name", "/dev/not-a-disk"}, 329 }) 330 } 331 332 func (s *diskSuite) TestDiskFromMountPointPartitionsHappy(c *C) { 333 restore := osutil.MockMountInfo(`130 30 42:4 / /run/mnt/data rw,relatime shared:54 - ext4 /dev/vda4 rw 334 130 30 42:4 / /run/mnt/ubuntu-boot rw,relatime shared:54 - ext4 /dev/vda3 rw 335 `) 336 defer restore() 337 338 restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) { 339 switch dev { 340 case "/dev/vda4", "/dev/vda3": 341 return map[string]string{ 342 "ID_PART_ENTRY_DISK": "42:0", 343 }, nil 344 case "/dev/block/42:1": 345 return map[string]string{ 346 // bios-boot does not have a filesystem label, so it shouldn't 347 // be found, but this is not fatal 348 "DEVTYPE": "partition", 349 "ID_PART_ENTRY_UUID": "bios-boot-partuuid", 350 }, nil 351 case "/dev/block/42:2": 352 return map[string]string{ 353 "DEVTYPE": "partition", 354 "ID_FS_LABEL_ENC": "ubuntu-seed", 355 "ID_PART_ENTRY_UUID": "ubuntu-seed-partuuid", 356 }, nil 357 case "/dev/block/42:3": 358 return map[string]string{ 359 "DEVTYPE": "partition", 360 "ID_FS_LABEL_ENC": "ubuntu-boot", 361 "ID_PART_ENTRY_UUID": "ubuntu-boot-partuuid", 362 }, nil 363 case "/dev/block/42:4": 364 return map[string]string{ 365 "DEVTYPE": "partition", 366 "ID_FS_LABEL_ENC": "ubuntu-data", 367 "ID_PART_ENTRY_UUID": "ubuntu-data-partuuid", 368 }, nil 369 case "/dev/block/42:5": 370 return nil, fmt.Errorf("Unknown device 42:5") 371 default: 372 c.Logf("unexpected udev device properties requested: %s", dev) 373 c.Fail() 374 return nil, fmt.Errorf("unexpected udev device") 375 376 } 377 }) 378 defer restore() 379 380 ubuntuDataDisk, err := disks.DiskFromMountPoint("/run/mnt/data", nil) 381 c.Assert(err, IsNil) 382 c.Assert(ubuntuDataDisk, Not(IsNil)) 383 c.Assert(ubuntuDataDisk.Dev(), Equals, "42:0") 384 385 // we have the ubuntu-seed, ubuntu-boot, and ubuntu-data partition labels 386 for _, label := range []string{"ubuntu-seed", "ubuntu-boot", "ubuntu-data"} { 387 id, err := ubuntuDataDisk.FindMatchingPartitionUUID(label) 388 c.Assert(err, IsNil) 389 c.Assert(id, Equals, label+"-partuuid") 390 } 391 392 // and the mountpoint for ubuntu-boot at /run/mnt/ubuntu-boot matches the 393 // same disk 394 matches, err := ubuntuDataDisk.MountPointIsFromDisk("/run/mnt/ubuntu-boot", nil) 395 c.Assert(err, IsNil) 396 c.Assert(matches, Equals, true) 397 398 // and we can find the partition for ubuntu-boot first and then match 399 // that with ubuntu-data too 400 ubuntuBootDisk, err := disks.DiskFromMountPoint("/run/mnt/ubuntu-boot", nil) 401 c.Assert(err, IsNil) 402 c.Assert(ubuntuBootDisk, Not(IsNil)) 403 c.Assert(ubuntuBootDisk.Dev(), Equals, "42:0") 404 405 // we have the ubuntu-seed, ubuntu-boot, and ubuntu-data partition labels 406 for _, label := range []string{"ubuntu-seed", "ubuntu-boot", "ubuntu-data"} { 407 id, err := ubuntuBootDisk.FindMatchingPartitionUUID(label) 408 c.Assert(err, IsNil) 409 c.Assert(id, Equals, label+"-partuuid") 410 } 411 412 // and the mountpoint for ubuntu-boot at /run/mnt/ubuntu-boot matches the 413 // same disk 414 matches, err = ubuntuBootDisk.MountPointIsFromDisk("/run/mnt/data", nil) 415 c.Assert(err, IsNil) 416 c.Assert(matches, Equals, true) 417 418 // finally we can't find the bios-boot partition because it has no label 419 _, err = ubuntuBootDisk.FindMatchingPartitionUUID("bios-boot") 420 c.Assert(err, ErrorMatches, "filesystem label \"bios-boot\" not found") 421 var notFoundErr disks.FilesystemLabelNotFoundError 422 c.Assert(xerrors.As(err, ¬FoundErr), Equals, true) 423 424 _, err = ubuntuDataDisk.FindMatchingPartitionUUID("bios-boot") 425 c.Assert(err, ErrorMatches, "filesystem label \"bios-boot\" not found") 426 c.Assert(xerrors.As(err, ¬FoundErr), Equals, true) 427 } 428 429 func (s *diskSuite) TestDiskFromMountPointDecryptedDevicePartitionsHappy(c *C) { 430 restore := osutil.MockMountInfo(`130 30 252:0 / /run/mnt/data rw,relatime shared:54 - ext4 /dev/mapper/ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4 rw 431 130 30 42:4 / /run/mnt/ubuntu-boot rw,relatime shared:54 - ext4 /dev/vda3 rw 432 `) 433 defer restore() 434 435 restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) { 436 switch dev { 437 case "/dev/mapper/ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4": 438 return map[string]string{ 439 // the mapper device is a disk/volume 440 "DEVTYPE": "disk", 441 }, nil 442 case "/dev/vda4", 443 "/dev/vda3", 444 "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304": 445 return map[string]string{ 446 "ID_PART_ENTRY_DISK": "42:0", 447 "DEVTYPE": "partition", 448 }, nil 449 case "/dev/block/42:1": 450 return map[string]string{ 451 // bios-boot does not have a filesystem label, so it shouldn't 452 // be found, but this is not fatal 453 "DEVTYPE": "partition", 454 "ID_PART_ENTRY_UUID": "bios-boot-partuuid", 455 }, nil 456 case "/dev/block/42:2": 457 return map[string]string{ 458 "DEVTYPE": "partition", 459 "ID_FS_LABEL_ENC": "ubuntu-seed", 460 "ID_PART_ENTRY_UUID": "ubuntu-seed-partuuid", 461 }, nil 462 case "/dev/block/42:3": 463 return map[string]string{ 464 "DEVTYPE": "partition", 465 "ID_FS_LABEL_ENC": "ubuntu-boot", 466 "ID_PART_ENTRY_UUID": "ubuntu-boot-partuuid", 467 }, nil 468 case "/dev/block/42:4": 469 return map[string]string{ 470 "DEVTYPE": "partition", 471 "ID_FS_LABEL_ENC": "ubuntu-data-enc", 472 "ID_PART_ENTRY_UUID": "ubuntu-data-enc-partuuid", 473 }, nil 474 case "/dev/block/42:5": 475 return nil, fmt.Errorf("Unknown device 42:5") 476 default: 477 c.Logf("unexpected udev device properties requested: %s", dev) 478 c.Fail() 479 return nil, fmt.Errorf("unexpected udev device") 480 481 } 482 }) 483 defer restore() 484 485 // mock the /sys/dev/block dir 486 devBlockDir := filepath.Join(dirs.SysfsDir, "dev", "block") 487 restore = disks.MockDevBlockDir(devBlockDir) 488 defer restore() 489 490 // mock the sysfs dm uuid and name files 491 dmDir := filepath.Join(devBlockDir, "252:0", "dm") 492 err := os.MkdirAll(dmDir, 0755) 493 c.Assert(err, IsNil) 494 495 b := []byte("ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4") 496 err = ioutil.WriteFile(filepath.Join(dmDir, "name"), b, 0644) 497 c.Assert(err, IsNil) 498 499 b = []byte("CRYPT-LUKS2-5a522809c87e4dfa81a88dc5667d1304-ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4") 500 err = ioutil.WriteFile(filepath.Join(dmDir, "uuid"), b, 0644) 501 c.Assert(err, IsNil) 502 503 opts := &disks.Options{IsDecryptedDevice: true} 504 ubuntuDataDisk, err := disks.DiskFromMountPoint("/run/mnt/data", opts) 505 c.Assert(err, IsNil) 506 c.Assert(ubuntuDataDisk, Not(IsNil)) 507 c.Assert(ubuntuDataDisk.Dev(), Equals, "42:0") 508 509 // we have the ubuntu-seed, ubuntu-boot, and ubuntu-data partition labels 510 for _, label := range []string{"ubuntu-seed", "ubuntu-boot", "ubuntu-data-enc"} { 511 id, err := ubuntuDataDisk.FindMatchingPartitionUUID(label) 512 c.Assert(err, IsNil) 513 c.Assert(id, Equals, label+"-partuuid") 514 } 515 516 // and the mountpoint for ubuntu-boot at /run/mnt/ubuntu-boot matches the 517 // same disk 518 matches, err := ubuntuDataDisk.MountPointIsFromDisk("/run/mnt/ubuntu-boot", nil) 519 c.Assert(err, IsNil) 520 c.Assert(matches, Equals, true) 521 522 // and we can find the partition for ubuntu-boot first and then match 523 // that with ubuntu-data too 524 ubuntuBootDisk, err := disks.DiskFromMountPoint("/run/mnt/ubuntu-boot", nil) 525 c.Assert(err, IsNil) 526 c.Assert(ubuntuBootDisk, Not(IsNil)) 527 c.Assert(ubuntuBootDisk.Dev(), Equals, "42:0") 528 529 // we have the ubuntu-seed, ubuntu-boot, and ubuntu-data partition labels 530 for _, label := range []string{"ubuntu-seed", "ubuntu-boot", "ubuntu-data-enc"} { 531 id, err := ubuntuBootDisk.FindMatchingPartitionUUID(label) 532 c.Assert(err, IsNil) 533 c.Assert(id, Equals, label+"-partuuid") 534 } 535 536 // and the mountpoint for ubuntu-boot at /run/mnt/ubuntu-boot matches the 537 // same disk 538 matches, err = ubuntuBootDisk.MountPointIsFromDisk("/run/mnt/data", opts) 539 c.Assert(err, IsNil) 540 c.Assert(matches, Equals, true) 541 }