github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/lib/filesystem/util/writeRaw.go (about)

     1  package util
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"syscall"
    16  	"text/template"
    17  	"time"
    18  	"unsafe"
    19  
    20  	"github.com/Cloud-Foundations/Dominator/lib/constants"
    21  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
    22  	"github.com/Cloud-Foundations/Dominator/lib/format"
    23  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    24  	"github.com/Cloud-Foundations/Dominator/lib/hash"
    25  	"github.com/Cloud-Foundations/Dominator/lib/log"
    26  	"github.com/Cloud-Foundations/Dominator/lib/mbr"
    27  	"github.com/Cloud-Foundations/Dominator/lib/objectserver"
    28  	"github.com/Cloud-Foundations/Dominator/lib/wsyscall"
    29  )
    30  
    31  const (
    32  	BLKGETSIZE  = 0x00001260
    33  	createFlags = os.O_CREATE | os.O_TRUNC | os.O_RDWR
    34  )
    35  
    36  var (
    37  	mutex               sync.Mutex
    38  	defaultMkfsFeatures map[string]struct{} // Key: feature name.
    39  	grubTemplate        = template.Must(template.New("grub").Parse(
    40  		grubTemplateString))
    41  )
    42  
    43  func checkIfPartition(device string) (bool, error) {
    44  	if isBlock, err := checkIsBlock(device); err != nil {
    45  		if !os.IsNotExist(err) {
    46  			return false, err
    47  		}
    48  		return false, nil
    49  	} else if !isBlock {
    50  		return false, fmt.Errorf("%s is not a block device", device)
    51  	} else {
    52  		return true, nil
    53  	}
    54  }
    55  
    56  func checkIsBlock(filename string) (bool, error) {
    57  	if fi, err := os.Stat(filename); err != nil {
    58  		if !os.IsNotExist(err) {
    59  			return false, fmt.Errorf("error stating: %s: %s", filename, err)
    60  		}
    61  		return false, err
    62  	} else {
    63  		return fi.Mode()&os.ModeDevice == os.ModeDevice, nil
    64  	}
    65  }
    66  
    67  func findExecutable(rootDir, file string) error {
    68  	if d, err := os.Stat(filepath.Join(rootDir, file)); err != nil {
    69  		return err
    70  	} else {
    71  		if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
    72  			return nil
    73  		}
    74  		return os.ErrPermission
    75  	}
    76  }
    77  
    78  func getBootDirectory(fs *filesystem.FileSystem) (
    79  	*filesystem.DirectoryInode, error) {
    80  	if fs.EntriesByName == nil {
    81  		fs.BuildEntryMap()
    82  	}
    83  	dirent, ok := fs.EntriesByName["boot"]
    84  	if !ok {
    85  		return nil, errors.New("missing /boot directory")
    86  	}
    87  	bootDirectory, ok := dirent.Inode().(*filesystem.DirectoryInode)
    88  	if !ok {
    89  		return nil, errors.New("/boot is not a directory")
    90  	}
    91  	return bootDirectory, nil
    92  }
    93  
    94  func getDefaultMkfsFeatures(device, size string, logger log.Logger) (
    95  	map[string]struct{}, error) {
    96  	mutex.Lock()
    97  	defer mutex.Unlock()
    98  	if defaultMkfsFeatures == nil {
    99  		startTime := time.Now()
   100  		logger.Println("Making calibration file-system")
   101  		cmd := exec.Command("mkfs.ext4", "-L", "calibration-fs", "-i", "65536",
   102  			device, size)
   103  		if output, err := cmd.CombinedOutput(); err != nil {
   104  			return nil, fmt.Errorf(
   105  				"error making calibration file-system on: %s: %s: %s",
   106  				device, err, output)
   107  		}
   108  		logger.Printf("Made calibration file-system in %s\n",
   109  			format.Duration(time.Since(startTime)))
   110  		cmd = exec.Command("dumpe2fs", "-h", device)
   111  		output, err := cmd.CombinedOutput()
   112  		if err != nil {
   113  			return nil, fmt.Errorf("error dumping file-system info: %s: %s",
   114  				err, output)
   115  		}
   116  		defaultMkfsFeatures = make(map[string]struct{})
   117  		for _, line := range strings.Split(string(output), "\n") {
   118  			fields := strings.Fields(line)
   119  			if len(fields) < 3 {
   120  				continue
   121  			}
   122  			if fields[0] != "Filesystem" || fields[1] != "features:" {
   123  				continue
   124  			}
   125  			for _, field := range fields[2:] {
   126  				defaultMkfsFeatures[field] = struct{}{}
   127  			}
   128  			break
   129  		}
   130  		// Scrub out the calibration file-system.
   131  		buffer := make([]byte, 65536)
   132  		if file, err := os.OpenFile(device, os.O_WRONLY, 0); err == nil {
   133  			file.Write(buffer)
   134  			file.Close()
   135  		}
   136  	}
   137  	return defaultMkfsFeatures, nil
   138  }
   139  
   140  func getUnsupportedOptions(fs *filesystem.FileSystem,
   141  	objectsGetter objectserver.ObjectsGetter) ([]string, error) {
   142  	bootDirectory, err := getBootDirectory(fs)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	dirent, ok := bootDirectory.EntriesByName["ext4.unsupported-features"]
   147  	var unsupportedOptions []string
   148  	if ok {
   149  		if inode, ok := dirent.Inode().(*filesystem.RegularInode); ok {
   150  			hashes := []hash.Hash{inode.Hash}
   151  			objectsReader, err := objectsGetter.GetObjects(hashes)
   152  			if err != nil {
   153  				return nil, err
   154  			}
   155  			defer objectsReader.Close()
   156  			size, reader, err := objectsReader.NextObject()
   157  			if err != nil {
   158  				return nil, err
   159  			}
   160  			defer reader.Close()
   161  			if size > 1024 {
   162  				return nil,
   163  					errors.New("/boot/ext4.unsupported-features is too large")
   164  			}
   165  			for {
   166  				var option string
   167  				_, err := fmt.Fscanf(reader, "%s\n", &option)
   168  				if err != nil {
   169  					if err == io.EOF {
   170  						break
   171  					}
   172  					return nil, err
   173  				} else {
   174  					unsupportedOptions = append(unsupportedOptions,
   175  						strings.Map(sanitiseInput, option))
   176  				}
   177  			}
   178  		}
   179  	}
   180  	return unsupportedOptions, nil
   181  }
   182  
   183  func getRootPartition(bootDevice string) (string, error) {
   184  	if isPartition, err := checkIfPartition(bootDevice + "p1"); err != nil {
   185  		return "", err
   186  	} else if isPartition {
   187  		return bootDevice + "p1", nil
   188  	}
   189  	if isPartition, err := checkIfPartition(bootDevice + "1"); err != nil {
   190  		return "", err
   191  	} else if !isPartition {
   192  		return "", errors.New("no root partition found")
   193  	} else {
   194  		return bootDevice + "1", nil
   195  	}
   196  }
   197  
   198  func getDeviceSize(device string) (uint64, error) {
   199  	fd, err := syscall.Open(device, os.O_RDONLY|syscall.O_CLOEXEC, 0666)
   200  	if err != nil {
   201  		return 0, fmt.Errorf("error opening: %s: %s", device, err)
   202  	}
   203  	defer syscall.Close(fd)
   204  	var statbuf syscall.Stat_t
   205  	if err := syscall.Fstat(fd, &statbuf); err != nil {
   206  		return 0, fmt.Errorf("error stating: %s: %s\n", device, err)
   207  	} else if statbuf.Mode&syscall.S_IFMT != syscall.S_IFBLK {
   208  		return 0, fmt.Errorf("%s is not a block device, mode: %0o",
   209  			device, statbuf.Mode)
   210  	}
   211  	var blk uint64
   212  	err = wsyscall.Ioctl(fd, BLKGETSIZE, uintptr(unsafe.Pointer(&blk)))
   213  	if err != nil {
   214  		return 0, fmt.Errorf("error geting device size: %s: %s", device, err)
   215  	}
   216  	return blk << 9, nil
   217  }
   218  
   219  func lookPath(rootDir, file string) (string, error) {
   220  	if strings.Contains(file, "/") {
   221  		if err := findExecutable(rootDir, file); err != nil {
   222  			return "", err
   223  		}
   224  		return file, nil
   225  	}
   226  	path := os.Getenv("PATH")
   227  	for _, dir := range filepath.SplitList(path) {
   228  		if dir == "" {
   229  			dir = "." // Unix shell semantics: path element "" means "."
   230  		}
   231  		path := filepath.Join(dir, file)
   232  		if err := findExecutable(rootDir, path); err == nil {
   233  			return path, nil
   234  		}
   235  	}
   236  	return "", fmt.Errorf("(chroot=%s) %s not found in PATH", rootDir, file)
   237  }
   238  
   239  func makeAndWriteRoot(fs *filesystem.FileSystem,
   240  	objectsGetter objectserver.ObjectsGetter, bootDevice, rootDevice string,
   241  	options WriteRawOptions, logger log.DebugLogger) error {
   242  	unsupportedOptions, err := getUnsupportedOptions(fs, objectsGetter)
   243  	if err != nil {
   244  		return err
   245  	}
   246  	var bootInfo *BootInfoType
   247  	if options.RootLabel == "" {
   248  		options.RootLabel = fmt.Sprintf("rootfs@%x", time.Now().Unix())
   249  	}
   250  	if options.InstallBootloader {
   251  		var err error
   252  		bootInfo, err = getBootInfo(fs, options.RootLabel, "net.ifnames=0")
   253  		if err != nil {
   254  			return err
   255  		}
   256  	}
   257  	err = makeExt4fs(rootDevice, options.RootLabel, unsupportedOptions, 8192,
   258  		logger)
   259  	if err != nil {
   260  		return err
   261  	}
   262  	mountPoint, err := ioutil.TempDir("", "write-raw-image")
   263  	if err != nil {
   264  		return err
   265  	}
   266  	defer os.RemoveAll(mountPoint)
   267  	err = wsyscall.Mount(rootDevice, mountPoint, "ext4", 0, "")
   268  	if err != nil {
   269  		return fmt.Errorf("error mounting: %s", rootDevice)
   270  	}
   271  	defer syscall.Unmount(mountPoint, 0)
   272  	os.RemoveAll(filepath.Join(mountPoint, "lost+found"))
   273  	if err := Unpack(fs, objectsGetter, mountPoint, logger); err != nil {
   274  		return err
   275  	}
   276  	if err := writeImageName(mountPoint, options.InitialImageName); err != nil {
   277  		return err
   278  	}
   279  	if options.WriteFstab {
   280  		err := writeRootFstabEntry(mountPoint, options.RootLabel)
   281  		if err != nil {
   282  			return err
   283  		}
   284  	}
   285  	if options.InstallBootloader {
   286  		err := bootInfo.installBootloader(bootDevice, mountPoint,
   287  			options.RootLabel, options.DoChroot, logger)
   288  		if err != nil {
   289  			return err
   290  		}
   291  	}
   292  	return nil
   293  }
   294  
   295  func makeBootable(fs *filesystem.FileSystem,
   296  	deviceName, rootLabel, rootDir, kernelOptions string,
   297  	doChroot bool, logger log.DebugLogger) error {
   298  	if err := writeRootFstabEntry(rootDir, rootLabel); err != nil {
   299  		return err
   300  	}
   301  	if bootInfo, err := getBootInfo(fs, rootLabel, kernelOptions); err != nil {
   302  		return err
   303  	} else {
   304  		return bootInfo.installBootloader(deviceName, rootDir, rootLabel,
   305  			doChroot, logger)
   306  	}
   307  }
   308  
   309  func makeExt4fs(deviceName, label string, unsupportedOptions []string,
   310  	bytesPerInode uint64, logger log.Logger) error {
   311  	size, err := getDeviceSize(deviceName)
   312  	if err != nil {
   313  		return err
   314  	}
   315  	sizeString := strconv.FormatUint(size>>10, 10)
   316  	var options []string
   317  	if len(unsupportedOptions) > 0 {
   318  		defaultFeatures, err := getDefaultMkfsFeatures(deviceName, sizeString,
   319  			logger)
   320  		if err != nil {
   321  			return err
   322  		}
   323  		for _, option := range unsupportedOptions {
   324  			if _, ok := defaultFeatures[option]; ok {
   325  				options = append(options, "^"+option)
   326  			}
   327  		}
   328  	}
   329  	cmd := exec.Command("mkfs.ext4")
   330  	if bytesPerInode != 0 {
   331  		cmd.Args = append(cmd.Args, "-i", strconv.FormatUint(bytesPerInode, 10))
   332  	}
   333  	if label != "" {
   334  		cmd.Args = append(cmd.Args, "-L", label)
   335  	}
   336  	if len(options) > 0 {
   337  		cmd.Args = append(cmd.Args, "-O", strings.Join(options, ","))
   338  	}
   339  	cmd.Args = append(cmd.Args, deviceName, sizeString)
   340  	startTime := time.Now()
   341  	if output, err := cmd.CombinedOutput(); err != nil {
   342  		return fmt.Errorf("error making file-system on: %s: %s: %s",
   343  			deviceName, err, output)
   344  	}
   345  	logger.Printf("Made %s file-system on: %s in %s\n",
   346  		format.FormatBytes(size), deviceName,
   347  		format.Duration(time.Since(startTime)))
   348  	return nil
   349  }
   350  
   351  func sanitiseInput(ch rune) rune {
   352  	if 'a' <= ch && ch <= 'z' {
   353  		return ch
   354  	} else if '0' <= ch && ch <= '9' {
   355  		return ch
   356  	} else if ch == '_' {
   357  		return ch
   358  	} else {
   359  		return -1
   360  	}
   361  }
   362  
   363  func getBootInfo(fs *filesystem.FileSystem, rootLabel string,
   364  	extraKernelOptions string) (*BootInfoType, error) {
   365  	bootDirectory, err := getBootDirectory(fs)
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  	bootInfo := &BootInfoType{
   370  		BootDirectory: bootDirectory,
   371  		KernelOptions: MakeKernelOptions("LABEL="+rootLabel,
   372  			extraKernelOptions),
   373  	}
   374  	for _, dirent := range bootDirectory.EntryList {
   375  		if strings.HasPrefix(dirent.Name, "initrd.img-") ||
   376  			strings.HasPrefix(dirent.Name, "initramfs-") {
   377  			if bootInfo.InitrdImageFile != "" {
   378  				return nil, errors.New("multiple initrd images")
   379  			}
   380  			bootInfo.InitrdImageDirent = dirent
   381  			bootInfo.InitrdImageFile = "/boot/" + dirent.Name
   382  		}
   383  		if strings.HasPrefix(dirent.Name, "vmlinuz-") {
   384  			if bootInfo.KernelImageFile != "" {
   385  				return nil, errors.New("multiple kernel images")
   386  			}
   387  			bootInfo.KernelImageDirent = dirent
   388  			bootInfo.KernelImageFile = "/boot/" + dirent.Name
   389  		}
   390  	}
   391  	return bootInfo, nil
   392  }
   393  
   394  func (bootInfo *BootInfoType) installBootloader(deviceName string,
   395  	rootDir, rootLabel string, doChroot bool, logger log.DebugLogger) error {
   396  	startTime := time.Now()
   397  	var bootDir, chrootDir string
   398  	if doChroot {
   399  		bootDir = "/boot"
   400  		chrootDir = rootDir
   401  	} else {
   402  		bootDir = filepath.Join(rootDir, "boot")
   403  	}
   404  	grubConfigFile := filepath.Join(rootDir, "boot", "grub", "grub.cfg")
   405  	grubInstaller, err := lookPath(chrootDir, "grub-install")
   406  	if err != nil {
   407  		grubInstaller, err = lookPath(chrootDir, "grub2-install")
   408  		if err != nil {
   409  			return fmt.Errorf("cannot find GRUB installer: %s", err)
   410  		}
   411  		grubConfigFile = filepath.Join(rootDir, "boot", "grub2", "grub.cfg")
   412  	}
   413  	cmd := exec.Command(grubInstaller, "--boot-directory="+bootDir, deviceName)
   414  	if doChroot {
   415  		cmd.Dir = "/"
   416  		cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: chrootDir}
   417  		logger.Debugf(0, "running(chroot=%s): %s %s\n",
   418  			chrootDir, cmd.Path, strings.Join(cmd.Args[1:], " "))
   419  	} else {
   420  		logger.Debugf(0, "running: %s %s\n",
   421  			cmd.Path, strings.Join(cmd.Args[1:], " "))
   422  	}
   423  	if output, err := cmd.CombinedOutput(); err != nil {
   424  		return fmt.Errorf("error installing GRUB on: %s: %s: %s",
   425  			deviceName, err, output)
   426  	}
   427  	logger.Printf("installed GRUB in %s\n",
   428  		format.Duration(time.Since(startTime)))
   429  	if err := bootInfo.writeGrubConfig(grubConfigFile); err != nil {
   430  		return err
   431  	}
   432  	return bootInfo.writeGrubTemplate(grubConfigFile + ".template")
   433  }
   434  
   435  func (bootInfo *BootInfoType) writeGrubConfig(filename string) error {
   436  	file, err := os.Create(filename)
   437  	if err != nil {
   438  		return fmt.Errorf("error creating GRUB config file: %s", err)
   439  	}
   440  	defer file.Close()
   441  	if err := grubTemplate.Execute(file, bootInfo); err != nil {
   442  		return err
   443  	}
   444  	return file.Close()
   445  }
   446  
   447  func (bootInfo *BootInfoType) writeGrubTemplate(filename string) error {
   448  	file, err := os.Create(filename)
   449  	if err != nil {
   450  		return fmt.Errorf("error creating GRUB config file template: %s", err)
   451  	}
   452  	defer file.Close()
   453  	if _, err := file.Write([]byte(grubTemplateString)); err != nil {
   454  		return err
   455  	}
   456  	return file.Close()
   457  }
   458  
   459  func (bootInfo *BootInfoType) writeBootloaderConfig(rootDir string,
   460  	logger log.Logger) error {
   461  	grubConfigFile := filepath.Join(rootDir, "boot", "grub", "grub.cfg")
   462  	_, err := lookPath("", "grub-install")
   463  	if err != nil {
   464  		_, err = lookPath("", "grub2-install")
   465  		if err != nil {
   466  			return fmt.Errorf("cannot find GRUB installer: %s", err)
   467  		}
   468  		grubConfigFile = filepath.Join(rootDir, "boot", "grub2", "grub.cfg")
   469  	}
   470  	if err := bootInfo.writeGrubConfig(grubConfigFile); err != nil {
   471  		return err
   472  	}
   473  	return bootInfo.writeGrubTemplate(grubConfigFile + ".template")
   474  }
   475  
   476  func writeFstabEntry(writer io.Writer,
   477  	source, mountPoint, fileSystemType, flags string,
   478  	dumpFrequency, checkOrder uint) error {
   479  	if flags == "" {
   480  		flags = "defaults"
   481  	}
   482  	_, err := fmt.Fprintf(writer, "%-22s %-10s %-5s %-10s %d %d\n",
   483  		source, mountPoint, fileSystemType, flags, dumpFrequency, checkOrder)
   484  	return err
   485  }
   486  
   487  func writeImageName(mountPoint, imageName string) error {
   488  	pathname := filepath.Join(mountPoint, constants.InitialImageNameFile)
   489  	if imageName == "" {
   490  		if err := os.Remove(pathname); err != nil {
   491  			if os.IsNotExist(err) {
   492  				return nil
   493  			}
   494  			return err
   495  		}
   496  	}
   497  	if err := os.MkdirAll(filepath.Dir(pathname), fsutil.DirPerms); err != nil {
   498  		return err
   499  	}
   500  	buffer := &bytes.Buffer{}
   501  	fmt.Fprintln(buffer, imageName)
   502  	return fsutil.CopyToFile(pathname, fsutil.PublicFilePerms, buffer, 0)
   503  }
   504  
   505  func writeToBlock(fs *filesystem.FileSystem,
   506  	objectsGetter objectserver.ObjectsGetter, bootDevice string,
   507  	tableType mbr.TableType, options WriteRawOptions,
   508  	logger log.DebugLogger) error {
   509  	if err := mbr.WriteDefault(bootDevice, tableType); err != nil {
   510  		return err
   511  	}
   512  	if rootDevice, err := getRootPartition(bootDevice); err != nil {
   513  		return err
   514  	} else {
   515  		return makeAndWriteRoot(fs, objectsGetter, bootDevice, rootDevice,
   516  			options, logger)
   517  	}
   518  }
   519  
   520  func writeToFile(fs *filesystem.FileSystem,
   521  	objectsGetter objectserver.ObjectsGetter, rawFilename string,
   522  	perm os.FileMode, tableType mbr.TableType, options WriteRawOptions,
   523  	logger log.DebugLogger) error {
   524  	tmpFilename := rawFilename + "~"
   525  	if file, err := os.OpenFile(tmpFilename, createFlags, perm); err != nil {
   526  		return err
   527  	} else {
   528  		file.Close()
   529  		defer os.Remove(tmpFilename)
   530  	}
   531  	usageEstimate := fs.EstimateUsage(0)
   532  	minBytes := usageEstimate + usageEstimate>>3 // 12% extra for good luck.
   533  	minBytes += options.MinimumFreeBytes
   534  	if options.RoundupPower < 24 {
   535  		options.RoundupPower = 24 // 16 MiB.
   536  	}
   537  	imageUnits := minBytes >> options.RoundupPower
   538  	if imageUnits<<options.RoundupPower < minBytes {
   539  		imageUnits++
   540  	}
   541  	imageSize := imageUnits << options.RoundupPower
   542  	if err := os.Truncate(tmpFilename, int64(imageSize)); err != nil {
   543  		return err
   544  	}
   545  	if err := mbr.WriteDefault(tmpFilename, tableType); err != nil {
   546  		return err
   547  	}
   548  	loopDevice, err := fsutil.LoopbackSetup(tmpFilename)
   549  	if err != nil {
   550  		return err
   551  	}
   552  	defer fsutil.LoopbackDelete(loopDevice)
   553  	err = makeAndWriteRoot(fs, objectsGetter, loopDevice, loopDevice+"p1",
   554  		options, logger)
   555  	if options.AllocateBlocks { // mkfs discards blocks, so do this after.
   556  		if err := fsutil.Fallocate(tmpFilename, imageSize); err != nil {
   557  			return err
   558  		}
   559  	}
   560  	if err != nil {
   561  		return err
   562  	}
   563  	return os.Rename(tmpFilename, rawFilename)
   564  }
   565  
   566  func writeRaw(fs *filesystem.FileSystem,
   567  	objectsGetter objectserver.ObjectsGetter, rawFilename string,
   568  	perm os.FileMode, tableType mbr.TableType, options WriteRawOptions,
   569  	logger log.DebugLogger) error {
   570  	if isBlock, err := checkIsBlock(rawFilename); err != nil {
   571  		if !os.IsNotExist(err) {
   572  			return err
   573  		}
   574  	} else if isBlock {
   575  		return writeToBlock(fs, objectsGetter, rawFilename, tableType,
   576  			options, logger)
   577  	}
   578  	return writeToFile(fs, objectsGetter, rawFilename, perm, tableType,
   579  		options, logger)
   580  }
   581  
   582  func writeRootFstabEntry(rootDir, rootLabel string) error {
   583  	file, err := os.Create(filepath.Join(rootDir, "etc", "fstab"))
   584  	if err != nil {
   585  		return err
   586  	} else {
   587  		defer file.Close()
   588  		return writeFstabEntry(file, "LABEL="+rootLabel, "/", "ext4",
   589  			"", 0, 1)
   590  	}
   591  }
   592  
   593  const grubTemplateString string = `# Generated from simple template.
   594  insmod serial
   595  serial --unit=0 --speed=115200
   596  terminal_output serial
   597  set timeout=0
   598  
   599  menuentry 'Linux' 'Solitary Linux' {
   600          insmod gzio
   601          insmod part_msdos
   602          insmod ext2
   603          echo    'Loading Linux {{.KernelImageFile}} ...'
   604          linux   {{.KernelImageFile}} {{.KernelOptions}}
   605          echo    'Loading initial ramdisk ...'
   606          initrd  {{.InitrdImageFile}}
   607  }
   608  `