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 }