github.com/emc-advanced-dev/unik@v0.0.0-20190717152701-a58d3e8e33b7/pkg/os/volumes.go (about)

     1  package os
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path"
     7  	"text/template"
     8  
     9  	"github.com/emc-advanced-dev/pkg/errors"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  )
    13  
    14  type RawVolume struct {
    15  	Path string `json:"Path"`
    16  	Size int64  `json:"Size"`
    17  }
    18  
    19  const GrubTemplate = `default=0
    20  fallback=1
    21  timeout=1
    22  hiddenmenu
    23  
    24  title Unik
    25  root {{.RootDrive}}
    26  kernel /boot/program.bin {{.CommandLine}}
    27  `
    28  
    29  const DeviceMapFile = `(hd0) {{.GrubDevice}}
    30  `
    31  
    32  const ProgramName = "program.bin"
    33  
    34  func createSparseFile(filename string, size DiskSize) error {
    35  	fd, err := os.Create(filename)
    36  	if err != nil {
    37  		return err
    38  	}
    39  	defer fd.Close()
    40  
    41  	_, err = fd.Seek(int64(size.ToBytes())-1, 0)
    42  	if err != nil {
    43  		return err
    44  	}
    45  	_, err = fd.Write([]byte{0})
    46  	if err != nil {
    47  		return err
    48  	}
    49  	return nil
    50  }
    51  
    52  func CreateBootImageWithSize(rootFile string, size DiskSize, progPath, staticFilesDir, commandline string, usePartitionTables bool) error {
    53  	err := createSparseFile(rootFile, size)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	log.WithFields(log.Fields{"imgFile": rootFile, "size": size.ToPartedFormat()}).Debug("created sparse file")
    58  
    59  	if usePartitionTables {
    60  		return CreateBootImageOnFile(rootFile, progPath, staticFilesDir, commandline)
    61  	}
    62  	return CreateBootImageOnFilePvGrub(rootFile, progPath, staticFilesDir, commandline)
    63  }
    64  
    65  func CreateBootImageOnFile(rootFile string, progPath, staticFilesDir, commandline string) error {
    66  
    67  	log.WithFields(log.Fields{"imgFile": rootFile}).Debug("attaching sparse file")
    68  	rootLo := NewLoDevice(rootFile)
    69  	rootLodName, err := rootLo.Acquire()
    70  	if err != nil {
    71  		return err
    72  	}
    73  	defer rootLo.Release()
    74  
    75  	log.Debug("device mapping to 'hda'")
    76  
    77  	// use device mapper to rename the lo device to something that grub likes more.
    78  	// like hda!
    79  	grubDiskName := "hda"
    80  
    81  	devTmp, err := ioutil.TempDir("/dev", "unik-tmp")
    82  	if err != nil {
    83  		return err
    84  	}
    85  	defer os.RemoveAll(devTmp)
    86  
    87  	rootDeviceName := path.Join(devTmp, grubDiskName)
    88  	if err := os.Link(rootLodName.Name(), rootDeviceName); err != nil {
    89  		return err
    90  	}
    91  
    92  	log.Debug("partitioning")
    93  
    94  	p := &MsDosPartioner{rootLodName.Name()}
    95  	if err := p.MakeTable(); err != nil {
    96  		return err
    97  	}
    98  	if err := p.MakePartTillEnd("primary", MegaBytes(2)); err != nil {
    99  		return err
   100  	}
   101  	// make the partition just created bootable
   102  	if err := p.Makebootable(1); err != nil {
   103  		return err
   104  	}
   105  	parts, err := ListParts(rootLodName)
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	if len(parts) < 1 {
   111  		return errors.New("No parts created", nil)
   112  	}
   113  
   114  	part := parts[0]
   115  
   116  	// get the block device
   117  	bootDevice, err := part.Acquire()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	defer part.Release()
   122  
   123  	firstPart := rootDeviceName + "1"
   124  	if err := os.Link(bootDevice.Name(), firstPart); err != nil {
   125  		return err
   126  	}
   127  
   128  	bootLabel := "boot"
   129  	// format the device and mount and copy
   130  	err = RunLogCommand("mkfs", "-L", bootLabel, "-I", "128", "-t", "ext2", bootDevice.Name())
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	mntPoint, err := MountDevice(firstPart)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	defer Umount(mntPoint)
   140  
   141  	if err := PrepareGrub(mntPoint, rootDeviceName, progPath, staticFilesDir, commandline); err != nil {
   142  		return err
   143  	}
   144  
   145  	err = RunLogCommand("grub-install", "--no-floppy", "--root-directory="+mntPoint, rootDeviceName)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  func PrepareGrub(folder, rootDeviceName, kernel, staticFilesDir, commandline string) error {
   154  	grubPath := path.Join(folder, "boot", "grub")
   155  	kernelDst := path.Join(folder, "boot", ProgramName)
   156  
   157  	os.MkdirAll(grubPath, 0755)
   158  
   159  	log.WithFields(log.Fields{"src": staticFilesDir, "dst": folder}).Debug("copying all files")
   160  	if err := CopyDir(staticFilesDir, folder); err != nil {
   161  		return err
   162  	}
   163  
   164  	// copy program.bin.. skip that for now
   165  	log.WithFields(log.Fields{"src": kernel, "dst": kernelDst}).Debug("copying file")
   166  	if err := CopyFile(kernel, kernelDst); err != nil {
   167  		return err
   168  	}
   169  
   170  	if err := writeBootTemplate(path.Join(grubPath, "menu.lst"), "(hd0,0)", commandline); err != nil {
   171  		return err
   172  	}
   173  
   174  	if err := writeBootTemplate(path.Join(grubPath, "grub.conf"), "(hd0,0)", commandline); err != nil {
   175  		return err
   176  	}
   177  
   178  	if err := writeDeviceMap(path.Join(grubPath, "device.map"), rootDeviceName); err != nil {
   179  		return err
   180  	}
   181  	return nil
   182  }
   183  
   184  func CreateBootImageOnFilePvGrub(rootFile string, progPath, staticFilesDir, commandline string) error {
   185  	log.WithFields(log.Fields{"imgFile": rootFile}).Debug("attaching sparse file")
   186  	rootLo := NewLoDevice(rootFile)
   187  	bootDevice, err := rootLo.Acquire()
   188  	if err != nil {
   189  		return err
   190  	}
   191  	defer rootLo.Release()
   192  
   193  	bootLabel := "boot"
   194  	// format the device and mount and copy
   195  	err = RunLogCommand("mkfs", "-L", bootLabel, "-I", "128", "-t", "ext2", bootDevice.Name())
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	mntPoint, err := Mount(bootDevice)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	defer Umount(mntPoint)
   205  
   206  	if err := PreparePVGrub(mntPoint, "sda1", progPath, staticFilesDir, commandline); err != nil {
   207  		return err
   208  	}
   209  
   210  	return nil
   211  }
   212  
   213  func PreparePVGrub(folder, rootDeviceName, kernel, staticFilesDir, commandline string) error {
   214  	grubPath := path.Join(folder, "boot", "grub")
   215  	kernelDst := path.Join(folder, "boot", ProgramName)
   216  
   217  	os.MkdirAll(grubPath, 0755)
   218  
   219  	log.WithFields(log.Fields{"src": staticFilesDir, "dst": folder}).Debug("copying all files")
   220  	if err := CopyDir(staticFilesDir, folder); err != nil {
   221  		return err
   222  	}
   223  
   224  	// copy program.bin.. skip that for now
   225  	log.WithFields(log.Fields{"src": kernel, "dst": kernelDst}).Debug("copying file")
   226  	if err := CopyFile(kernel, kernelDst); err != nil {
   227  		return err
   228  	}
   229  
   230  	if err := writeBootTemplate(path.Join(grubPath, "menu.lst"), "(hd0)", commandline); err != nil {
   231  		return err
   232  	}
   233  
   234  	if err := writeBootTemplate(path.Join(grubPath, "grub.conf"), "(hd0)", commandline); err != nil {
   235  		return err
   236  	}
   237  
   238  	if err := writeDeviceMap(path.Join(grubPath, "device.map"), rootDeviceName); err != nil {
   239  		return err
   240  	}
   241  	return nil
   242  }
   243  
   244  func writeDeviceMap(fname, rootDevice string) error {
   245  	f, err := os.Create(fname)
   246  	if err != nil {
   247  		return err
   248  	}
   249  	defer f.Close()
   250  
   251  	t := template.Must(template.New("devicemap").Parse(DeviceMapFile))
   252  
   253  	log.WithFields(log.Fields{"device": rootDevice, "file": fname}).Debug("Writing device map")
   254  	if err := t.Execute(f, struct {
   255  		GrubDevice string
   256  	}{rootDevice}); err != nil {
   257  		return err
   258  	}
   259  
   260  	return nil
   261  }
   262  func writeBootTemplate(fname, rootDrive, commandline string) error {
   263  	log.WithFields(log.Fields{"fname": fname, "rootDrive": rootDrive, "commandline": commandline}).Debug("writing boot template")
   264  
   265  	f, err := os.Create(fname)
   266  	if err != nil {
   267  		return err
   268  	}
   269  	defer f.Close()
   270  
   271  	t := template.Must(template.New("grub").Parse(GrubTemplate))
   272  
   273  	if err := t.Execute(f, struct {
   274  		RootDrive   string
   275  		CommandLine string
   276  	}{rootDrive, commandline}); err != nil {
   277  		return err
   278  	}
   279  
   280  	return nil
   281  
   282  }
   283  
   284  func formatDeviceAndCopyContents(folder string, volType string, dev BlockDevice) error {
   285  	var err error
   286  	switch volType {
   287  	case "fat":
   288  		err = RunLogCommand("mkfs.fat", dev.Name())
   289  	case "ext2":
   290  		fallthrough
   291  	case "":
   292  		err = RunLogCommand("mkfs", "-I", "128", "-t", "ext2", dev.Name())
   293  	default:
   294  		return errors.New("Unknown fs type", nil)
   295  	}
   296  
   297  	if err != nil {
   298  		return err
   299  	}
   300  
   301  	mntPoint, err := Mount(dev)
   302  	if err != nil {
   303  		return err
   304  	}
   305  	defer Umount(mntPoint)
   306  
   307  	if err := CopyDir(folder, mntPoint); err != nil {
   308  		return err
   309  	}
   310  	return nil
   311  }
   312  
   313  func CreateSingleVolume(rootFile string, volType string, folder RawVolume) error {
   314  	ext2Overhead := MegaBytes(2).ToBytes()
   315  
   316  	size := folder.Size
   317  
   318  	if size == 0 {
   319  		var err error
   320  		size, err = GetDirSize(folder.Path)
   321  		if err != nil {
   322  			return err
   323  		}
   324  	}
   325  
   326  	// take a spare sizde and down to sector size
   327  	size = (SectorSize + size + size/10 + int64(ext2Overhead))
   328  	size &^= (SectorSize - 1)
   329  	// 10% buffer.. aligned to 512
   330  	sizeVolume := Bytes(size)
   331  
   332  	if _, err := ToSectors(Bytes(size)); err != nil {
   333  		return err
   334  	}
   335  
   336  	if err := createSparseFile(rootFile, sizeVolume); err != nil {
   337  		return err
   338  	}
   339  
   340  	return CopyToImgFile(folder.Path, volType, rootFile)
   341  }
   342  
   343  func CopyToImgFile(folder, volType string, imgfile string) error {
   344  	imgLo := NewLoDevice(imgfile)
   345  	imgLodName, err := imgLo.Acquire()
   346  	if err != nil {
   347  		return err
   348  	}
   349  	defer imgLo.Release()
   350  
   351  	return formatDeviceAndCopyContents(folder, volType, imgLodName)
   352  
   353  }
   354  
   355  func copyToPart(folder string, volType string, part Resource) error {
   356  	imgLodName, err := part.Acquire()
   357  	if err != nil {
   358  		return err
   359  	}
   360  	defer part.Release()
   361  	return formatDeviceAndCopyContents(folder, volType, imgLodName)
   362  }
   363  
   364  func CreateVolumes(imgFile string, volType string, volumes []RawVolume, newPartitioner func(device string) Partitioner) error {
   365  	if len(volumes) == 0 {
   366  		return nil
   367  	}
   368  
   369  	var sizes []Bytes
   370  
   371  	ext2Overhead := MegaBytes(2).ToBytes()
   372  	firstPartOffest := MegaBytes(2).ToBytes()
   373  	var totalSize Bytes = 0
   374  
   375  	log.Debug("Calculating sizes")
   376  
   377  	for _, v := range volumes {
   378  		if v.Size == 0 {
   379  			cursize, err := GetDirSize(v.Path)
   380  			if err != nil {
   381  				return err
   382  			}
   383  			sizes = append(sizes, Bytes(cursize)+ext2Overhead)
   384  		} else {
   385  			sizes = append(sizes, Bytes(v.Size))
   386  		}
   387  		totalSize += sizes[len(sizes)-1]
   388  	}
   389  	sizeDrive := Bytes((SectorSize + totalSize + totalSize/10) &^ (SectorSize - 1))
   390  	sizeDrive += MegaBytes(4).ToBytes()
   391  
   392  	log.WithFields(log.Fields{"imgFile": imgFile, "size": totalSize.ToPartedFormat()}).Debug("Creating image file")
   393  	err := createSparseFile(imgFile, sizeDrive)
   394  	if err != nil {
   395  		return err
   396  	}
   397  
   398  	imgLo := NewLoDevice(imgFile)
   399  	imgLodName, err := imgLo.Acquire()
   400  	if err != nil {
   401  		return err
   402  	}
   403  	defer imgLo.Release()
   404  
   405  	p := newPartitioner(imgLodName.Name())
   406  
   407  	p.MakeTable()
   408  	var start Bytes = firstPartOffest
   409  	for _, curSize := range sizes {
   410  		end := start + curSize
   411  		log.WithFields(log.Fields{"start": start, "end": end}).Debug("Creating partition")
   412  
   413  		err := p.MakePart(toPartedVolType(volType), start, end)
   414  		if err != nil {
   415  			return err
   416  		}
   417  		curParts, err := ListParts(imgLodName)
   418  		if err != nil {
   419  			return err
   420  		}
   421  		start = curParts[len(curParts)-1].Offset().ToBytes() + curParts[len(curParts)-1].Size().ToBytes()
   422  	}
   423  
   424  	parts, err := ListParts(imgLodName)
   425  
   426  	if len(parts) != len(volumes) {
   427  		return errors.New("Not enough parts created!", nil)
   428  	}
   429  
   430  	log.WithFields(log.Fields{"parts": parts, "volsize": sizes}).Debug("Creating volumes")
   431  	for i, v := range volumes {
   432  
   433  		if err := copyToPart(v.Path, volType, parts[i]); err != nil {
   434  			return err
   435  		}
   436  	}
   437  
   438  	return nil
   439  }
   440  
   441  func toPartedVolType(volType string) string {
   442  	switch volType {
   443  	case "fat":
   444  		return "fat32"
   445  	default:
   446  		return volType
   447  	}
   448  }