github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/installer/configureStorage.go (about)

     1  // +build linux
     2  
     3  package main
     4  
     5  import (
     6  	"bufio"
     7  	"bytes"
     8  	"crypto/rand"
     9  	"encoding/gob"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"os"
    15  	"path/filepath"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  	"syscall"
    21  	"time"
    22  
    23  	imageclient "github.com/Cloud-Foundations/Dominator/imageserver/client"
    24  	"github.com/Cloud-Foundations/Dominator/lib/concurrent"
    25  	"github.com/Cloud-Foundations/Dominator/lib/cpusharer"
    26  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
    27  	"github.com/Cloud-Foundations/Dominator/lib/filesystem/util"
    28  	"github.com/Cloud-Foundations/Dominator/lib/format"
    29  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    30  	"github.com/Cloud-Foundations/Dominator/lib/image"
    31  	"github.com/Cloud-Foundations/Dominator/lib/json"
    32  	"github.com/Cloud-Foundations/Dominator/lib/log"
    33  	"github.com/Cloud-Foundations/Dominator/lib/objectserver"
    34  	objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client"
    35  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    36  	"github.com/Cloud-Foundations/Dominator/lib/wsyscall"
    37  	fm_proto "github.com/Cloud-Foundations/Dominator/proto/fleetmanager"
    38  	installer_proto "github.com/Cloud-Foundations/Dominator/proto/installer"
    39  )
    40  
    41  const (
    42  	keyFile = "/etc/crypt.key"
    43  )
    44  
    45  type driveType struct {
    46  	discarded bool
    47  	devpath   string
    48  	name      string
    49  	size      uint64 // Bytes
    50  }
    51  
    52  type kexecRebooter struct {
    53  	util.BootInfoType
    54  	logger log.DebugLogger
    55  }
    56  
    57  func init() {
    58  	gob.Register(&filesystem.RegularInode{})
    59  	gob.Register(&filesystem.SymlinkInode{})
    60  	gob.Register(&filesystem.SpecialInode{})
    61  	gob.Register(&filesystem.DirectoryInode{})
    62  }
    63  
    64  func closeEncryptedVolumes(logger log.DebugLogger) error {
    65  	if file, err := os.Open("/dev/mapper"); err != nil {
    66  		return err
    67  	} else {
    68  		defer file.Close()
    69  		if names, err := file.Readdirnames(-1); err != nil {
    70  			return err
    71  		} else {
    72  			for _, name := range names {
    73  				if name == "control" {
    74  					continue
    75  				}
    76  				err := run("cryptsetup", *tmpRoot, logger, "close", name)
    77  				if err != nil {
    78  					return err
    79  				}
    80  			}
    81  			return nil
    82  		}
    83  	}
    84  }
    85  
    86  func configureBootDrive(cpuSharer cpusharer.CpuSharer, drive *driveType,
    87  	layout installer_proto.StorageLayout, bootPartition int, img *image.Image,
    88  	objGetter objectserver.ObjectsGetter, logger log.DebugLogger) error {
    89  	startTime := time.Now()
    90  	if run("blkdiscard", *tmpRoot, logger, drive.devpath) == nil {
    91  		drive.discarded = true
    92  		logger.Printf("discarded %s in %s\n",
    93  			drive.devpath, format.Duration(time.Since(startTime)))
    94  	} else { // Erase old partition.
    95  		if err := eraseStart(drive.devpath, logger); err != nil {
    96  			return err
    97  		}
    98  	}
    99  	args := []string{"-s", "-a", "cylinder", drive.devpath, "mklabel", "msdos"}
   100  	unitSize := uint64(1 << 20)
   101  	unitSuffix := "MiB"
   102  	offsetInUnits := uint64(1)
   103  	for _, partition := range layout.BootDriveLayout {
   104  		sizeInUnits := partition.MinimumFreeBytes / unitSize
   105  		if sizeInUnits*unitSize < partition.MinimumFreeBytes {
   106  			sizeInUnits++
   107  		}
   108  		args = append(args, "mkpart", "primary", "ext2",
   109  			strconv.FormatUint(offsetInUnits, 10)+unitSuffix,
   110  			strconv.FormatUint(offsetInUnits+sizeInUnits, 10)+unitSuffix)
   111  		offsetInUnits += sizeInUnits
   112  	}
   113  	args = append(args, "mkpart", "primary", "ext2",
   114  		strconv.FormatUint(offsetInUnits, 10)+unitSuffix, "100%")
   115  	args = append(args,
   116  		"set", strconv.FormatInt(int64(bootPartition), 10), "boot", "on")
   117  	if err := run("parted", *tmpRoot, logger, args...); err != nil {
   118  		return err
   119  	}
   120  	// Prepare all file-systems concurrently, make them serially.
   121  	concurrentState := concurrent.NewState(uint(
   122  		len(layout.BootDriveLayout) + 1))
   123  	var mkfsMutex sync.Mutex
   124  	for index, partition := range layout.BootDriveLayout {
   125  		device := partitionName(drive.devpath, index+1)
   126  		partition := partition
   127  		err := concurrentState.GoRun(func() error {
   128  			return drive.makeFileSystem(cpuSharer, device, partition.MountPoint,
   129  				"ext4", &mkfsMutex, 0, logger)
   130  		})
   131  		if err != nil {
   132  			return err
   133  		}
   134  	}
   135  	concurrentState.GoRun(func() error {
   136  		device := partitionName(drive.devpath, len(layout.BootDriveLayout)+1)
   137  		return drive.makeFileSystem(cpuSharer, device,
   138  			layout.ExtraMountPointsBasename+"0", "ext4", &mkfsMutex, 65536,
   139  			logger)
   140  	})
   141  	if err := concurrentState.Reap(); err != nil {
   142  		return err
   143  	}
   144  	// Mount all file-systems, except the data file-system.
   145  	for index, partition := range layout.BootDriveLayout {
   146  		device := partitionName(drive.devpath, index+1)
   147  		err := mount(remapDevice(device, partition.MountPoint),
   148  			filepath.Join(*mountPoint, partition.MountPoint), "ext4", logger)
   149  		if err != nil {
   150  			return err
   151  		}
   152  	}
   153  	return installRoot(drive.devpath, img.FileSystem, objGetter, logger)
   154  }
   155  
   156  func configureDataDrive(cpuSharer cpusharer.CpuSharer, drive *driveType,
   157  	index int, layout installer_proto.StorageLayout,
   158  	logger log.DebugLogger) error {
   159  	startTime := time.Now()
   160  	if run("blkdiscard", *tmpRoot, logger, drive.devpath) == nil {
   161  		drive.discarded = true
   162  		logger.Printf("discarded %s in %s\n",
   163  			drive.devpath, format.Duration(time.Since(startTime)))
   164  	}
   165  	dataMountPoint := layout.ExtraMountPointsBasename + strconv.FormatInt(
   166  		int64(index), 10)
   167  	return drive.makeFileSystem(cpuSharer, drive.devpath, dataMountPoint,
   168  		"ext4", nil, 1048576, logger)
   169  }
   170  
   171  func configureStorage(config fm_proto.GetMachineInfoResponse,
   172  	logger log.DebugLogger) (Rebooter, error) {
   173  	startTime := time.Now()
   174  	var layout installer_proto.StorageLayout
   175  	err := json.ReadFromFile(filepath.Join(*tftpDirectory,
   176  		"storage-layout.json"),
   177  		&layout)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	var bootPartition, rootPartition int
   182  	for index, partition := range layout.BootDriveLayout {
   183  		switch partition.MountPoint {
   184  		case "/":
   185  			rootPartition = index + 1
   186  		case "/boot":
   187  			bootPartition = index + 1
   188  		}
   189  	}
   190  	if rootPartition < 1 {
   191  		return nil, fmt.Errorf("no root partition specified in layout")
   192  	}
   193  	if bootPartition < 1 {
   194  		bootPartition = rootPartition
   195  	}
   196  	drives, err := listDrives(logger)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	rootDevice := partitionName(drives[0].devpath, rootPartition)
   201  	randomKey, err := getRandomKey(16, logger)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	imageName, img, client, err := getImage(logger)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  	defer client.Close()
   210  	if img == nil {
   211  		logger.Println("no image specified, skipping paritioning")
   212  		return nil, nil
   213  	} else {
   214  		if err := img.FileSystem.RebuildInodePointers(); err != nil {
   215  			return nil, err
   216  		}
   217  		imageSize := img.FileSystem.EstimateUsage(0)
   218  		if layout.BootDriveLayout[rootPartition-1].MinimumFreeBytes <
   219  			imageSize {
   220  			layout.BootDriveLayout[rootPartition-1].MinimumFreeBytes = imageSize
   221  		}
   222  		layout.BootDriveLayout[rootPartition-1].MinimumFreeBytes += imageSize
   223  	}
   224  	var rebooter Rebooter
   225  	if layout.UseKexec {
   226  		bootInfo, err := util.GetBootInfo(img.FileSystem, "rootfs", "")
   227  		if err != nil {
   228  			return nil, err
   229  		}
   230  		rebooter = kexecRebooter{
   231  			BootInfoType: *bootInfo,
   232  			logger:       logger,
   233  		}
   234  	}
   235  	objClient := objectclient.AttachObjectClient(client)
   236  	defer objClient.Close()
   237  	objGetter, err := createObjectsCache(img.FileSystem.GetObjects(), objClient,
   238  		rootDevice, logger)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  	if err := installTmpRoot(img.FileSystem, objGetter, logger); err != nil {
   243  		return nil, err
   244  	}
   245  	err = ioutil.WriteFile(filepath.Join(*tmpRoot, keyFile), randomKey,
   246  		fsutil.PrivateFilePerms)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	for index := range randomKey { // Scrub key.
   251  		randomKey[index] = 0
   252  	}
   253  	// Configure all drives concurrently, making file-systems.
   254  	// Use concurrent package because of it's reaping cabability.
   255  	// Use cpusharer package to limit CPU intensive operations.
   256  	concurrentState := concurrent.NewState(uint(len(drives)))
   257  	cpuSharer := cpusharer.NewFifoCpuSharer()
   258  	err = concurrentState.GoRun(func() error {
   259  		return configureBootDrive(cpuSharer, drives[0], layout, bootPartition,
   260  			img, objGetter, logger)
   261  	})
   262  	if err != nil {
   263  		return nil, concurrentState.Reap()
   264  	}
   265  	for index, drive := range drives[1:] {
   266  		drive := drive
   267  		index := index + 1
   268  		err := concurrentState.GoRun(func() error {
   269  			return configureDataDrive(cpuSharer, drive, index, layout, logger)
   270  		})
   271  		if err != nil {
   272  			break
   273  		}
   274  	}
   275  	if err := concurrentState.Reap(); err != nil {
   276  		return nil, err
   277  	}
   278  	// Make table entries for the boot device file-systems, except data FS.
   279  	fsTab := &bytes.Buffer{}
   280  	cryptTab := &bytes.Buffer{}
   281  	for index, partition := range layout.BootDriveLayout {
   282  		device := partitionName(drives[0].devpath, index+1)
   283  		err = drives[0].writeDeviceEntries(device, partition.MountPoint, "ext4",
   284  			fsTab, cryptTab, uint(index+1))
   285  		if err != nil {
   286  			return nil, err
   287  		}
   288  	}
   289  	// Make table entries for data file-systems.
   290  	for index, drive := range drives {
   291  		checkCount := uint(2)
   292  		var device string
   293  		if index == 0 { // The boot device is partitioned.
   294  			checkCount = uint(len(layout.BootDriveLayout) + 1)
   295  			device = partitionName(drives[0].devpath,
   296  				len(layout.BootDriveLayout)+1)
   297  		} else { // Extra drives are used whole.
   298  			device = drive.devpath
   299  		}
   300  		dataMountPoint := layout.ExtraMountPointsBasename + strconv.FormatInt(
   301  			int64(index), 10)
   302  		err = drive.writeDeviceEntries(device, dataMountPoint, "ext4", fsTab,
   303  			cryptTab, checkCount)
   304  		if err != nil {
   305  			return nil, err
   306  		}
   307  	}
   308  	err = ioutil.WriteFile(filepath.Join(*mountPoint, "etc", "fstab"),
   309  		fsTab.Bytes(), fsutil.PublicFilePerms)
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  	err = ioutil.WriteFile(filepath.Join(*mountPoint, "/etc", "crypttab"),
   314  		cryptTab.Bytes(), fsutil.PublicFilePerms)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  	// Copy key file and scrub temporary copy.
   319  	tmpKeyFile := filepath.Join(*tmpRoot, keyFile)
   320  	err = fsutil.CopyFile(filepath.Join(*mountPoint, keyFile),
   321  		tmpKeyFile, fsutil.PrivateFilePerms)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	if file, err := os.OpenFile(tmpKeyFile, os.O_WRONLY, 0); err != nil {
   326  		return nil, err
   327  	} else {
   328  		defer file.Close()
   329  		if _, err := file.Write(randomKey); err != nil {
   330  			return nil, err
   331  		}
   332  	}
   333  	logdir := filepath.Join(*mountPoint, "var", "log", "installer")
   334  	if err := os.MkdirAll(logdir, fsutil.DirPerms); err != nil {
   335  		return nil, err
   336  	}
   337  	if err := fsutil.CopyTree(logdir, *tftpDirectory); err != nil {
   338  		return nil, err
   339  	}
   340  	if err := util.WriteImageName(*mountPoint, imageName); err != nil {
   341  		return nil, err
   342  	}
   343  	logger.Printf("configureStorage() took %s\n",
   344  		format.Duration(time.Since(startTime)))
   345  	return rebooter, nil
   346  }
   347  
   348  func eraseStart(device string, logger log.DebugLogger) error {
   349  	if *dryRun {
   350  		logger.Debugf(0, "dry run: skipping erasure of: %s\n", device)
   351  		return nil
   352  	}
   353  	logger.Debugf(0, "erasing start of: %s\n", device)
   354  	file, err := os.OpenFile(device, os.O_WRONLY, 0)
   355  	if err != nil {
   356  		return err
   357  	}
   358  	defer file.Close()
   359  	var buffer [65536]byte
   360  	if _, err := file.Write(buffer[:]); err != nil {
   361  		return err
   362  	}
   363  	return nil
   364  }
   365  
   366  func getImage(logger log.DebugLogger) (
   367  	string, *image.Image, *srpc.Client, error) {
   368  	data, err := ioutil.ReadFile(filepath.Join(*tftpDirectory, "imagename"))
   369  	if err != nil {
   370  		if os.IsNotExist(err) {
   371  			return "", nil, nil, nil
   372  		}
   373  		return "", nil, nil, err
   374  	}
   375  	imageName := strings.TrimSpace(string(data))
   376  	data, err = ioutil.ReadFile(filepath.Join(*tftpDirectory, "imageserver"))
   377  	if err != nil {
   378  		return "", nil, nil, err
   379  	}
   380  	imageServerAddress := strings.TrimSpace(string(data))
   381  	logger.Printf("dialing imageserver: %s\n", imageServerAddress)
   382  	startTime := time.Now()
   383  	client, err := srpc.DialHTTP("tcp", imageServerAddress, time.Second*15)
   384  	if err != nil {
   385  		return "", nil, nil, err
   386  	}
   387  	logger.Printf("dialed imageserver after: %s\n",
   388  		format.Duration(time.Since(startTime)))
   389  	startTime = time.Now()
   390  	if img, _ := imageclient.GetImage(client, imageName); img != nil {
   391  		logger.Debugf(0, "got image: %s in %s\n",
   392  			imageName, format.Duration(time.Since(startTime)))
   393  		return imageName, img, client, nil
   394  	}
   395  	streamName := imageName
   396  	isDir, err := imageclient.CheckDirectory(client, streamName)
   397  	if err != nil {
   398  		client.Close()
   399  		return "", nil, nil, err
   400  	}
   401  	if !isDir {
   402  		streamName = filepath.Dir(streamName)
   403  		isDir, err = imageclient.CheckDirectory(client, streamName)
   404  		if err != nil {
   405  			client.Close()
   406  			return "", nil, nil, err
   407  		}
   408  	}
   409  	if !isDir {
   410  		client.Close()
   411  		return "", nil, nil, fmt.Errorf("%s is not a directory", streamName)
   412  	}
   413  	imageName, err = imageclient.FindLatestImage(client, streamName, false)
   414  	if err != nil {
   415  		client.Close()
   416  		return "", nil, nil, err
   417  	}
   418  	if imageName == "" {
   419  		client.Close()
   420  		return "", nil, nil, fmt.Errorf("no image found in: %s on: %s",
   421  			streamName, imageServerAddress)
   422  	}
   423  	startTime = time.Now()
   424  	if img, err := imageclient.GetImage(client, imageName); err != nil {
   425  		client.Close()
   426  		return "", nil, nil, err
   427  	} else {
   428  		logger.Debugf(0, "got image: %s in %s\n",
   429  			imageName, format.Duration(time.Since(startTime)))
   430  		return imageName, img, client, nil
   431  	}
   432  }
   433  
   434  func getRandomKey(numBytes uint, logger log.DebugLogger) ([]byte, error) {
   435  	logger.Printf("getting %d random bytes\n", numBytes)
   436  	timer := time.AfterFunc(time.Second, func() {
   437  		logger.Println("getting random data is taking too long")
   438  		logger.Println("mash on the keyboard to add entropy")
   439  	})
   440  	startTime := time.Now()
   441  	buffer := make([]byte, numBytes)
   442  	_, err := rand.Read(buffer)
   443  	timer.Stop()
   444  	if err != nil {
   445  		return nil, err
   446  	} else {
   447  		logger.Printf("read %d bytes of random data after %s\n",
   448  			numBytes, format.Duration(time.Since(startTime)))
   449  		return buffer, nil
   450  	}
   451  }
   452  
   453  func installRoot(device string, fileSystem *filesystem.FileSystem,
   454  	objGetter objectserver.ObjectsGetter, logger log.DebugLogger) error {
   455  	if *dryRun {
   456  		logger.Debugln(0, "dry run: skipping installing root")
   457  		return nil
   458  	}
   459  	logger.Debugln(0, "unpacking root")
   460  	err := util.Unpack(fileSystem, objGetter, *mountPoint, logger)
   461  	if err != nil {
   462  		return err
   463  	}
   464  	err = wsyscall.Mount("/dev", filepath.Join(*mountPoint, "dev"), "",
   465  		wsyscall.MS_BIND, "")
   466  	if err != nil {
   467  		return err
   468  	}
   469  	err = wsyscall.Mount("/proc", filepath.Join(*mountPoint, "proc"), "",
   470  		wsyscall.MS_BIND, "")
   471  	if err != nil {
   472  		return err
   473  	}
   474  	err = wsyscall.Mount("/sys", filepath.Join(*mountPoint, "sys"), "",
   475  		wsyscall.MS_BIND, "")
   476  	if err != nil {
   477  		return err
   478  	}
   479  	return util.MakeBootable(fileSystem, device, "rootfs", *mountPoint, "",
   480  		true, logger)
   481  }
   482  
   483  func installTmpRoot(fileSystem *filesystem.FileSystem,
   484  	objGetter objectserver.ObjectsGetter, logger log.DebugLogger) error {
   485  	if fi, err := os.Stat(*tmpRoot); err == nil {
   486  		if fi.IsDir() {
   487  			logger.Debugln(0, "tmproot already exists, not installing")
   488  			return nil
   489  		}
   490  	}
   491  	if *dryRun {
   492  		logger.Debugln(0, "dry run: skipping unpacking tmproot")
   493  		return nil
   494  	}
   495  	logger.Debugln(0, "unpacking tmproot")
   496  	if err := os.MkdirAll(*tmpRoot, fsutil.DirPerms); err != nil {
   497  		return err
   498  	}
   499  	syscall.Unmount(filepath.Join(*tmpRoot, "sys"), 0)
   500  	syscall.Unmount(filepath.Join(*tmpRoot, "proc"), 0)
   501  	syscall.Unmount(filepath.Join(*tmpRoot, "dev"), 0)
   502  	syscall.Unmount(*tmpRoot, 0)
   503  	if err := wsyscall.Mount("none", *tmpRoot, "tmpfs", 0, ""); err != nil {
   504  		return err
   505  	}
   506  	if err := util.Unpack(fileSystem, objGetter, *tmpRoot, logger); err != nil {
   507  		return err
   508  	}
   509  	err := wsyscall.Mount("/dev", filepath.Join(*tmpRoot, "dev"), "",
   510  		wsyscall.MS_BIND, "")
   511  	if err != nil {
   512  		return err
   513  	}
   514  	err = wsyscall.Mount("/proc", filepath.Join(*tmpRoot, "proc"), "",
   515  		wsyscall.MS_BIND, "")
   516  	if err != nil {
   517  		return err
   518  	}
   519  	err = wsyscall.Mount("/sys", filepath.Join(*tmpRoot, "sys"), "",
   520  		wsyscall.MS_BIND, "")
   521  	if err != nil {
   522  		return err
   523  	}
   524  	os.Symlink("/proc/mounts", filepath.Join(*tmpRoot, "etc", "mtab"))
   525  	return nil
   526  }
   527  
   528  func listDrives(logger log.DebugLogger) ([]*driveType, error) {
   529  	basedir := filepath.Join(*sysfsDirectory, "class", "block")
   530  	file, err := os.Open(basedir)
   531  	if err != nil {
   532  		return nil, err
   533  	}
   534  	names, err := file.Readdirnames(-1)
   535  	file.Close()
   536  	if err != nil {
   537  		return nil, err
   538  	}
   539  	sort.Strings(names)
   540  	var drives []*driveType
   541  	for _, name := range names {
   542  		dirname := filepath.Join(basedir, name)
   543  		if _, err := os.Stat(filepath.Join(dirname, "partition")); err == nil {
   544  			logger.Debugf(2, "skipping partition: %s\n", name)
   545  			continue
   546  		}
   547  		if _, err := os.Stat(filepath.Join(dirname, "device")); err != nil {
   548  			if !os.IsNotExist(err) {
   549  				return nil, err
   550  			}
   551  			logger.Debugf(2, "skipping non-device: %s\n", name)
   552  			continue
   553  		}
   554  		if v, err := readInt(filepath.Join(dirname, "removable")); err != nil {
   555  			return nil, err
   556  		} else if v != 0 {
   557  			logger.Debugf(2, "skipping removable device: %s\n", name)
   558  			continue
   559  		}
   560  		if val, err := readInt(filepath.Join(dirname, "size")); err != nil {
   561  			return nil, err
   562  		} else {
   563  			logger.Debugf(1, "found: %s %d GiB (%d GB)\n",
   564  				name, val>>21, val<<9/1000000000)
   565  			drives = append(drives, &driveType{
   566  				devpath: filepath.Join("/dev", name),
   567  				name:    name,
   568  				size:    val << 9,
   569  			})
   570  		}
   571  	}
   572  	if len(drives) < 1 {
   573  		return nil, fmt.Errorf("no drives found")
   574  	}
   575  	return drives, nil
   576  }
   577  
   578  func mount(source string, target string, fstype string,
   579  	logger log.DebugLogger) error {
   580  	if *dryRun {
   581  		logger.Debugf(0, "dry run: skipping mount of %s on %s type=%s\n",
   582  			source, target, fstype)
   583  		return nil
   584  	}
   585  	logger.Debugf(0, "mount %s on %s type=%s\n", source, target, fstype)
   586  	if err := os.MkdirAll(target, fsutil.DirPerms); err != nil {
   587  		return err
   588  	}
   589  	return syscall.Mount(source, target, fstype, 0, "")
   590  }
   591  
   592  func partitionName(devpath string, partitionNumber int) string {
   593  	devLeafName := filepath.Base(devpath)
   594  	partitionName := "p" + strconv.FormatInt(int64(partitionNumber), 10)
   595  	_, err := os.Stat(filepath.Join("/sys/class/block", devLeafName,
   596  		devLeafName+partitionName))
   597  	if err == nil {
   598  		return devpath + partitionName
   599  	}
   600  	return devpath + strconv.FormatInt(int64(partitionNumber), 10)
   601  }
   602  
   603  func readInt(filename string) (uint64, error) {
   604  	if file, err := os.Open(filename); err != nil {
   605  		return 0, err
   606  	} else {
   607  		defer file.Close()
   608  		var value uint64
   609  		if nVal, err := fmt.Fscanf(file, "%d\n", &value); err != nil {
   610  			return 0, err
   611  		} else if nVal != 1 {
   612  			return 0, fmt.Errorf("read %d values, expected 1", nVal)
   613  		} else {
   614  			return value, nil
   615  		}
   616  	}
   617  }
   618  
   619  func remapDevice(device, target string) string {
   620  	if target == "/" {
   621  		return device
   622  	} else {
   623  		return filepath.Join("/dev/mapper", filepath.Base(device))
   624  	}
   625  }
   626  
   627  func unmountStorage(logger log.DebugLogger) error {
   628  	syscall.Sync()
   629  	time.Sleep(time.Millisecond * 100)
   630  	file, err := os.Open("/proc/mounts")
   631  	if err != nil {
   632  		return err
   633  	}
   634  	defer file.Close()
   635  	var mountPoints []string
   636  	scanner := bufio.NewScanner(file)
   637  	for scanner.Scan() {
   638  		fields := strings.Fields(scanner.Text())
   639  		if len(fields) < 2 {
   640  			continue
   641  		} else {
   642  			if strings.HasPrefix(fields[1], *mountPoint) {
   643  				mountPoints = append(mountPoints, fields[1])
   644  			}
   645  		}
   646  	}
   647  	if err := scanner.Err(); err != nil {
   648  		return err
   649  	}
   650  	unmountedMainMountPoint := false
   651  	for index := len(mountPoints) - 1; index >= 0; index-- {
   652  		mntPoint := mountPoints[index]
   653  		if mntPoint == *mountPoint {
   654  			if err := closeEncryptedVolumes(logger); err != nil {
   655  				return err
   656  			}
   657  		}
   658  		if err := syscall.Unmount(mntPoint, 0); err != nil {
   659  			return fmt.Errorf("error unmounting: %s: %s", mntPoint, err)
   660  		} else {
   661  			logger.Debugf(2, "unmounted: %s\n", mntPoint)
   662  		}
   663  		if mntPoint == *mountPoint {
   664  			unmountedMainMountPoint = true
   665  		}
   666  	}
   667  	if !unmountedMainMountPoint {
   668  		return errors.New("did not find main mount point to unmount")
   669  	}
   670  	syscall.Sync()
   671  	return nil
   672  }
   673  
   674  func (drive driveType) cryptSetup(cpuSharer cpusharer.CpuSharer, device string,
   675  	logger log.DebugLogger) error {
   676  	cpuSharer.GrabCpu()
   677  	defer cpuSharer.ReleaseCpu()
   678  	startTime := time.Now()
   679  	err := run("cryptsetup", *tmpRoot, logger, "--verbose",
   680  		"--key-file", keyFile,
   681  		"--cipher", "aes-xts-plain64", "--key-size", "512",
   682  		"--hash", "sha512", "--iter-time", "5000", "--use-urandom",
   683  		"luksFormat", device)
   684  	if err != nil {
   685  		return err
   686  	}
   687  	logger.Printf("formatted encrypted device %s in %s\n",
   688  		device, time.Since(startTime))
   689  	startTime = time.Now()
   690  	if drive.discarded {
   691  		err = run("cryptsetup", *tmpRoot, logger, "open", "--type", "luks",
   692  			"--allow-discards",
   693  			"--key-file", keyFile, device, filepath.Base(device))
   694  	} else {
   695  		err = run("cryptsetup", *tmpRoot, logger, "open", "--type", "luks",
   696  			"--key-file", keyFile, device, filepath.Base(device))
   697  	}
   698  	if err != nil {
   699  		return err
   700  	}
   701  	logger.Printf("opened encrypted device %s in %s\n",
   702  		device, time.Since(startTime))
   703  	return nil
   704  }
   705  
   706  func (drive driveType) makeFileSystem(cpuSharer cpusharer.CpuSharer,
   707  	device, target, fstype string, mkfsMutex *sync.Mutex, bytesPerInode uint,
   708  	logger log.DebugLogger) error {
   709  	label := target
   710  	erase := true
   711  	if label == "/" {
   712  		label = "rootfs"
   713  		if drive.discarded {
   714  			erase = false
   715  		}
   716  	} else {
   717  		if err := drive.cryptSetup(cpuSharer, device, logger); err != nil {
   718  			return err
   719  		}
   720  		device = filepath.Join("/dev/mapper", filepath.Base(device))
   721  	}
   722  	if erase {
   723  		if err := eraseStart(device, logger); err != nil {
   724  			return err
   725  		}
   726  	}
   727  	var err error
   728  	if mkfsMutex != nil {
   729  		mkfsMutex.Lock()
   730  	}
   731  	startTime := time.Now()
   732  	if bytesPerInode > 0 {
   733  		err = run("mkfs.ext4", *tmpRoot, logger,
   734  			"-i", strconv.Itoa(int(bytesPerInode)), "-L", label,
   735  			"-E", "lazy_itable_init=0,lazy_journal_init=0", device)
   736  	} else {
   737  		err = run("mkfs.ext4", *tmpRoot, logger, "-L", label,
   738  			"-E", "lazy_itable_init=0,lazy_journal_init=0", device)
   739  	}
   740  	if mkfsMutex != nil {
   741  		mkfsMutex.Unlock()
   742  	}
   743  	if err != nil {
   744  		return err
   745  	}
   746  	logger.Printf("made file-system on %s in %s\n",
   747  		device, time.Since(startTime))
   748  	return nil
   749  }
   750  
   751  func (drive driveType) writeDeviceEntries(device, target, fstype string,
   752  	fsTab, cryptTab io.Writer, checkOrder uint) error {
   753  	label := target
   754  	if label == "/" {
   755  		label = "rootfs"
   756  	} else {
   757  		var options string
   758  		if drive.discarded {
   759  			options = "discard"
   760  		}
   761  		_, err := fmt.Fprintf(cryptTab, "%-15s %-23s %-15s %s\n",
   762  			filepath.Base(device), device, keyFile, options)
   763  		if err != nil {
   764  			return err
   765  		}
   766  	}
   767  	var fsFlags string
   768  	if drive.discarded {
   769  		fsFlags = "discard"
   770  	}
   771  	return util.WriteFstabEntry(fsTab, "LABEL="+label, target, fstype, fsFlags,
   772  		0, checkOrder)
   773  }
   774  
   775  func (rebooter kexecRebooter) Reboot() error {
   776  	return run("kexec", *tmpRoot, rebooter.logger,
   777  		"-l", rebooter.KernelImageFile,
   778  		"--append="+rebooter.KernelOptions,
   779  		"--console-serial", "--serial-baud=115200",
   780  		"--initrd="+rebooter.InitrdImageFile,
   781  		"-f")
   782  }
   783  
   784  func (rebooter kexecRebooter) String() string {
   785  	return "kexec"
   786  }