The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/distrobuilder/pull/368
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From 5994aadde58bd4bc7a36f6ad72e811f03fd65bfb Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 13:24:47 +0200 Subject: [PATCH 01/10] sources: Add Windows source Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- sources/source.go | 2 ++ sources/windows.go | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 sources/windows.go diff --git a/sources/source.go b/sources/source.go index fce32c5..cf33b34 100644 --- a/sources/source.go +++ b/sources/source.go @@ -44,6 +44,8 @@ func Get(name string) Downloader { return NewVoidLinuxHTTP() case "funtoo-http": return NewFuntooHTTP() + case "windows": + return NewWindows() } return nil diff --git a/sources/windows.go b/sources/windows.go new file mode 100644 index 0000000..b2c619d --- /dev/null +++ b/sources/windows.go @@ -0,0 +1,86 @@ +package sources + +import ( + "bytes" + "fmt" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + + lxd "github.com/lxc/lxd/shared" + "github.com/pkg/errors" + + "github.com/lxc/distrobuilder/shared" +) + +// Windows represents the Windows OS +type Windows struct{} + +// NewWindows creates a new Windows instance. +func NewWindows() *Windows { + return &Windows{} +} + +// Run unpacks a Windows VHD file. +func (s *Windows) Run(definition shared.Definition, rootfsDir string) error { + // URL + u, err := url.Parse(definition.Source.URL) + if err != nil { + return errors.Wrap(err, "Failed to parse URL") + } + + if u.Scheme != "file" { + return fmt.Errorf("Scheme %q is not supported", u.Scheme) + } + + rawFilePath := fmt.Sprintf("%s.raw", u.Path) + + if !lxd.PathExists(rawFilePath) { + // Convert the vhd image to raw. + err = shared.RunCommand("qemu-img", "convert", "-O", "raw", u.Path, rawFilePath) + if err != nil { + return errors.Wrap(err, "Failed to convert image") + } + } + + // Figure out the offset + var buf bytes.Buffer + + err = lxd.RunCommandWithFds(nil, &buf, "fdisk", "-l", "-o", "Start", rawFilePath) + if err != nil { + return errors.Wrap(err, "Failed to list partitions") + } + + output := strings.Split(buf.String(), "\n") + offsetStr := strings.TrimSpace(output[len(output)-2]) + + offset, err := strconv.Atoi(offsetStr) + if err != nil { + return errors.Wrap(err, "Failed to read offset") + } + + roRootDir := filepath.Join(os.TempDir(), "distrobuilder", "rootfs.ro") + + err = os.MkdirAll(roRootDir, 0755) + if err != nil { + return errors.Wrap(err, "Failed to create directory") + } + defer os.RemoveAll(filepath.Join(os.TempDir(), "distrobuilder")) + + // Mount the partition read-only since we don't want to accidently modify it. + err = shared.RunCommand("mount", "-o", fmt.Sprintf("ro,loop,offset=%d", offset*512), + rawFilePath, roRootDir) + if err != nil { + return errors.Wrap(err, "Failed to mount partition read-only") + } + + // Copy the read-only rootfs to the real rootfs. + err = shared.RunCommand("rsync", "-qa", roRootDir+"/", rootfsDir) + if err != nil { + return errors.Wrap(err, "Failed to copy rootfs") + } + + return nil +} From 4e082ef4b1505926441647065c667acd629c7b0e Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 13:25:36 +0200 Subject: [PATCH 02/10] shared/definition: Accept Windows as a source Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- shared/definition.go | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/definition.go b/shared/definition.go index 5512cfe..1989795 100644 --- a/shared/definition.go +++ b/shared/definition.go @@ -336,6 +336,7 @@ func (d *Definition) Validate() error { "plamolinux-http", "voidlinux-http", "funtoo-http", + "windows", } if !shared.StringInSlice(strings.TrimSpace(d.Source.Downloader), validDownloaders) { return fmt.Errorf("source.downloader must be one of %v", validDownloaders) From 38feac9ce9829761840fd1004295b391bed7b8d7 Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 13:34:59 +0200 Subject: [PATCH 03/10] shared/definition: Ignore package manager when using Windows Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- shared/definition.go | 80 +++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/shared/definition.go b/shared/definition.go index 1989795..dccdde7 100644 --- a/shared/definition.go +++ b/shared/definition.go @@ -342,51 +342,53 @@ func (d *Definition) Validate() error { return fmt.Errorf("source.downloader must be one of %v", validDownloaders) } - if d.Packages.Manager != "" { - validManagers := []string{ - "apk", - "apt", - "dnf", - "egoportage", - "opkg", - "pacman", - "portage", - "yum", - "equo", - "xbps", - "zypper", - "luet", - } - if !shared.StringInSlice(strings.TrimSpace(d.Packages.Manager), validManagers) { - return fmt.Errorf("packages.manager must be one of %v", validManagers) - } + if d.Source.Downloader != "windows" { + if d.Packages.Manager != "" { + validManagers := []string{ + "apk", + "apt", + "dnf", + "egoportage", + "opkg", + "pacman", + "portage", + "yum", + "equo", + "xbps", + "zypper", + "luet", + } + if !shared.StringInSlice(strings.TrimSpace(d.Packages.Manager), validManagers) { + return fmt.Errorf("packages.manager must be one of %v", validManagers) + } - if d.Packages.CustomManager != nil { - return fmt.Errorf("cannot have both packages.manager and packages.custom-manager set") - } - } else { - if d.Packages.CustomManager == nil { - return fmt.Errorf("packages.manager or packages.custom-manager needs to be set") - } + if d.Packages.CustomManager != nil { + return fmt.Errorf("cannot have both packages.manager and packages.custom-manager set") + } + } else { + if d.Packages.CustomManager == nil { + return fmt.Errorf("packages.manager or packages.custom-manager needs to be set") + } - if d.Packages.CustomManager.Clean.Command == "" { - return fmt.Errorf("packages.custom-manager requires a clean command") - } + if d.Packages.CustomManager.Clean.Command == "" { + return fmt.Errorf("packages.custom-manager requires a clean command") + } - if d.Packages.CustomManager.Install.Command == "" { - return fmt.Errorf("packages.custom-manager requires an install command") - } + if d.Packages.CustomManager.Install.Command == "" { + return fmt.Errorf("packages.custom-manager requires an install command") + } - if d.Packages.CustomManager.Remove.Command == "" { - return fmt.Errorf("packages.custom-manager requires a remove command") - } + if d.Packages.CustomManager.Remove.Command == "" { + return fmt.Errorf("packages.custom-manager requires a remove command") + } - if d.Packages.CustomManager.Refresh.Command == "" { - return fmt.Errorf("packages.custom-manager requires a refresh command") - } + if d.Packages.CustomManager.Refresh.Command == "" { + return fmt.Errorf("packages.custom-manager requires a refresh command") + } - if d.Packages.CustomManager.Update.Command == "" { - return fmt.Errorf("packages.custom-manager requires an update command") + if d.Packages.CustomManager.Update.Command == "" { + return fmt.Errorf("packages.custom-manager requires an update command") + } } } From 79691c4b107572f93982441f67e0c41c9f0fd9f0 Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 13:48:17 +0200 Subject: [PATCH 04/10] distrobuilder: Rename VM functions Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- distrobuilder/main_lxd.go | 17 +++------ distrobuilder/vm.go | 80 +++++++++++++++++++-------------------- 2 files changed, 46 insertions(+), 51 deletions(-) diff --git a/distrobuilder/main_lxd.go b/distrobuilder/main_lxd.go index 2446ee6..2e1d67f 100644 --- a/distrobuilder/main_lxd.go +++ b/distrobuilder/main_lxd.go @@ -239,25 +239,20 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error } defer vm.umountImage() - err = vm.createRootFS() + err = vm.createFilesystems() if err != nil { - return errors.Wrap(err, "Failed to create root filesystem") + return errors.Wrap(err, "Failed to create filesystems") } - err = vm.mountRootPartition() + err = vm.mountRootFilesystem() if err != nil { - return errors.Wrap(err, "failed to mount root partion") + return errors.Wrap(err, "Failed to mount root filesystem") } defer lxd.RunCommand("umount", "-R", vmDir) - err = vm.createUEFIFS() + err = vm.mountUEFIFilesystem() if err != nil { - return errors.Wrap(err, "Failed to create UEFI filesystem") - } - - err = vm.mountUEFIPartition() - if err != nil { - return errors.Wrap(err, "Failed to mount UEFI partition") + return errors.Wrap(err, "Failed to mount UEFI filesystem") } // We cannot use LXD's rsync package as that uses the --delete flag which diff --git a/distrobuilder/vm.go b/distrobuilder/vm.go index f6ba300..7939cfa 100644 --- a/distrobuilder/vm.go +++ b/distrobuilder/vm.go @@ -194,41 +194,6 @@ func (v *vm) umountImage() error { return nil } -func (v *vm) createRootFS() error { - if v.loopDevice == "" { - return fmt.Errorf("Disk image not mounted") - } - - switch v.rootFS { - case "btrfs": - err := shared.RunCommand("mkfs.btrfs", "-f", "-L", "rootfs", v.getRootfsDevFile()) - if err != nil { - return err - } - - // Create the root subvolume as well - err = shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir) - if err != nil { - return err - } - defer shared.RunCommand("umount", v.rootfsDir) - - return shared.RunCommand("btrfs", "subvolume", "create", fmt.Sprintf("%s/@", v.rootfsDir)) - case "ext4": - return shared.RunCommand("mkfs.ext4", "-F", "-b", "4096", "-i 8192", "-m", "0", "-L", "rootfs", "-E", "resize=536870912", v.getRootfsDevFile()) - } - - return nil -} - -func (v *vm) createUEFIFS() error { - if v.loopDevice == "" { - return fmt.Errorf("Disk image not mounted") - } - - return shared.RunCommand("mkfs.vfat", "-F", "32", "-n", "UEFI", v.getUEFIDevFile()) -} - func (v *vm) getRootfsPartitionUUID() (string, error) { if v.loopDevice == "" { return "", fmt.Errorf("Disk image not mounted") @@ -255,23 +220,58 @@ func (v *vm) getUEFIPartitionUUID() (string, error) { return strings.TrimSpace(stdout), nil } -func (v *vm) mountRootPartition() error { +func (v *vm) createFilesystems() error { if v.loopDevice == "" { return fmt.Errorf("Disk image not mounted") } + var err error + + // Create root filesystem switch v.rootFS { case "btrfs": - return shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir, "-o", "defaults,subvol=/@") + err := shared.RunCommand("mkfs.btrfs", "-f", "-L", "rootfs", v.getRootfsDevFile()) + if err != nil { + return err + } + + // Create the root subvolume as well + err = shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir) + if err != nil { + return err + } + defer shared.RunCommand("umount", v.rootfsDir) + + err = shared.RunCommand("btrfs", "subvolume", "create", fmt.Sprintf("%s/@", v.rootfsDir)) case "ext4": - return shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir) + err = shared.RunCommand("mkfs.ext4", "-F", "-b", "4096", "-i 8192", "-m", "0", "-L", "rootfs", "-E", "resize=536870912", v.getRootfsDevFile()) + } + if err != nil { + return err + } + + // Create UEFI filesystem + return shared.RunCommand("mkfs.vfat", "-F", "32", "-n", "UEFI", v.getUEFIDevFile()) +} +func (v *vm) mountRootFilesystem() error { + if v.loopDevice == "" { + return fmt.Errorf("Disk image not mounted") } - return nil + var err error + + switch v.rootFS { + case "btrfs": + err = shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir, "-o", "defaults,subvol=/@") + case "ext4": + err = shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir) + } + + return err } -func (v *vm) mountUEFIPartition() error { +func (v *vm) mountUEFIFilesystem() error { if v.loopDevice == "" { return fmt.Errorf("Disk image not mounted") } From fd261a161341397895e96e4b6025d8e81b35c40f Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 13:55:09 +0200 Subject: [PATCH 05/10] distrobuilder/vm: Add OS type Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- distrobuilder/vm.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/distrobuilder/vm.go b/distrobuilder/vm.go index 7939cfa..18b666d 100644 --- a/distrobuilder/vm.go +++ b/distrobuilder/vm.go @@ -14,6 +14,16 @@ import ( "github.com/lxc/distrobuilder/shared" ) +// OS represents the operating system. +type OS int + +const ( + // OSLinux represents the Linux operating system. + OSLinux OS = iota + // OSWindows represents the Windows operating system. + OSWindows +) + type vm struct { imageFile string loopDevice string From 4baf9d48a12cb0bcb4464899d5f631a4283e148e Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 14:04:21 +0200 Subject: [PATCH 06/10] distrobuilder: Add OS arg to newVM Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- distrobuilder/main_lxd.go | 9 ++++++++- distrobuilder/vm.go | 21 +++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/distrobuilder/main_lxd.go b/distrobuilder/main_lxd.go index 2e1d67f..e4e9313 100644 --- a/distrobuilder/main_lxd.go +++ b/distrobuilder/main_lxd.go @@ -202,6 +202,13 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error var mounts []shared.ChrootMount var vmDir string var vm *vm + var targetOS OS + + if c.global.definition.Source.Downloader == "windows" { + targetOS = OSWindows + } else { + targetOS = OSLinux + } if c.flagVM { vmDir = filepath.Join(c.global.flagCacheDir, "vm") @@ -218,7 +225,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error imgFile := filepath.Join(c.global.flagCacheDir, imgFilename) - vm, err = newVM(imgFile, vmDir, c.global.definition.Targets.LXD.VM.Filesystem, c.global.definition.Targets.LXD.VM.Size) + vm, err = newVM(imgFile, vmDir, c.global.definition.Targets.LXD.VM.Filesystem, c.global.definition.Targets.LXD.VM.Size, targetOS) if err != nil { return errors.Wrap(err, "Failed to instanciate VM") } diff --git a/distrobuilder/vm.go b/distrobuilder/vm.go index 18b666d..b601238 100644 --- a/distrobuilder/vm.go +++ b/distrobuilder/vm.go @@ -30,14 +30,27 @@ type vm struct { rootFS string rootfsDir string size uint64 + os OS } -func newVM(imageFile, rootfsDir, fs string, size uint64) (*vm, error) { +func newVM(imageFile, rootfsDir, fs string, size uint64, os OS) (*vm, error) { if fs == "" { - fs = "ext4" + if os == OSLinux { + fs = "ext4" + } else { + fs = "ntfs" + } + } + + var supportedFs []string + + if os == OSLinux { + supportedFs = []string{"btrfs", "ext4"} + } else { + supportedFs = []string{"ntfs"} } - if !lxd.StringInSlice(fs, []string{"btrfs", "ext4"}) { + if !lxd.StringInSlice(fs, supportedFs) { return nil, fmt.Errorf("Unsupported fs: %s", fs) } @@ -45,7 +58,7 @@ func newVM(imageFile, rootfsDir, fs string, size uint64) (*vm, error) { size = 4294967296 } - return &vm{imageFile: imageFile, rootfsDir: rootfsDir, rootFS: fs, size: size}, nil + return &vm{imageFile: imageFile, rootfsDir: rootfsDir, rootFS: fs, size: size, os: os}, nil } func (v *vm) getLoopDev() string { From b19477349558a389191de4925dfd4b4d837d217e Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 14:09:17 +0200 Subject: [PATCH 07/10] distrobuilder/vm: Add function getDiskUUID Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- distrobuilder/vm.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/distrobuilder/vm.go b/distrobuilder/vm.go index b601238..15049c6 100644 --- a/distrobuilder/vm.go +++ b/distrobuilder/vm.go @@ -308,3 +308,16 @@ func (v *vm) mountUEFIFilesystem() error { return shared.RunCommand("mount", v.getUEFIDevFile(), mountpoint) } + +func (v *vm) getDiskUUID() (string, error) { + if v.loopDevice == "" { + return "", fmt.Errorf("Disk image not mounted") + } + + stdout, err := lxd.RunCommand("blkid", "-s", "PTUUID", "-o", "value", v.loopDevice) + if err != nil { + return "", err + } + + return strings.TrimSpace(stdout), nil +} From a6b2724fa74e84274b3c125507c9852b69f717ae Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 14:09:48 +0200 Subject: [PATCH 08/10] distrobuilder/chroot: Add function replaceUUID Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- distrobuilder/chroot.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/distrobuilder/chroot.go b/distrobuilder/chroot.go index 242f87e..90df838 100644 --- a/distrobuilder/chroot.go +++ b/distrobuilder/chroot.go @@ -1,11 +1,14 @@ package main import ( + "bytes" "fmt" + "io/ioutil" "os" "path/filepath" "strings" + "github.com/google/uuid" "github.com/pkg/errors" "golang.org/x/sys/unix" @@ -199,3 +202,26 @@ func getOverlay(cacheDir, sourceDir string) (func(), string, error) { return cleanup, overlayDir, nil } + +func replaceUUID(path string, old uuid.UUID, new uuid.UUID) error { + f := func(u uuid.UUID) []byte { + b, err := u.MarshalBinary() + if err != nil { + return nil + } + + out := []byte{b[3], b[2], b[1], b[0], b[5], b[4], b[7], b[6]} + out = append(out, b[8:]...) + + return out + } + + read, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + write := bytes.ReplaceAll(read, f(old), f(new)) + + return ioutil.WriteFile(path, write, 0644) +} From 395c359a7393553629beeb62b20a88c29d2a16ce Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 14:13:00 +0200 Subject: [PATCH 09/10] shared/definition: Add VM specific targets Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- shared/definition.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/shared/definition.go b/shared/definition.go index dccdde7..f83425c 100644 --- a/shared/definition.go +++ b/shared/definition.go @@ -153,10 +153,20 @@ type DefinitionTargetLXC struct { Config []DefinitionTargetLXCConfig `yaml:"config,omitempty"` } +// DefinitionTargetLXDVMUUID represents the old partition UUIDs which are to be replaced by the newly generated ones. +type DefinitionTargetLXDVMUUID struct { + EFI string `yaml:"efi,omitempty"` + Data string `yaml:"data,omitempty"` + Disk string `yaml:"disk,omitempty"` +} + // DefinitionTargetLXDVM represents LXD VM specific options. type DefinitionTargetLXDVM struct { - Size uint64 `yaml:"size,omitempty"` - Filesystem string `yaml:"filesystem,omitempty"` + Size uint64 `yaml:"size,omitempty"` + Filesystem string `yaml:"filesystem,omitempty"` + BCD string `yaml:"bcd,omitempty"` + MSR string `yaml:"msr,omitempty"` + UUID DefinitionTargetLXDVMUUID `yaml:"uuid,omitempty"` } // DefinitionTargetLXD represents LXD specific options. From d832a657e564d3aa66d8351b9fa6b5c154faab74 Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 14 Oct 2020 14:17:37 +0200 Subject: [PATCH 10/10] distrobuilder: Support building Windows VM images Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- distrobuilder/main.go | 27 ++++++++ distrobuilder/main_lxd.go | 140 +++++++++++++++++++++++++++++++++----- distrobuilder/vm.go | 70 +++++++++++++++---- 3 files changed, 205 insertions(+), 32 deletions(-) diff --git a/distrobuilder/main.go b/distrobuilder/main.go index bc6ac88..b9f78ad 100644 --- a/distrobuilder/main.go +++ b/distrobuilder/main.go @@ -223,6 +223,28 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error { return err } + // Sanity checks for Windows VMs. + if c.definition.Source.Downloader == "windows" { + subcommand := cmd.CalledAs() + + if strings.Contains(subcommand, "lxc") { + return fmt.Errorf("Windows is not supported by LXC") + } + + if subcommand == "build-lxd" || subcommand == "pack-lxd" { + ok, err := cmd.Flags().GetBool("vm") + if err != nil { + return err + } + + if !ok { + return fmt.Errorf("LXD only supports Windows VMs") + } + + c.definition.Targets.Type = "vm" + } + } + // Create cache directory if we also plan on creating LXC or LXD images if !isRunningBuildDir { err = os.MkdirAll(c.flagCacheDir, 0755) @@ -251,6 +273,11 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Error while downloading source") } + // Return here as we cannot use chroot for Windows. + if c.definition.Source.Downloader == "windows" { + return nil + } + // Setup the mounts and chroot into the rootfs exitChroot, err := shared.SetupChroot(c.sourceDir, c.definition.Environment, nil) if err != nil { diff --git a/distrobuilder/main_lxd.go b/distrobuilder/main_lxd.go index e4e9313..b2a8cb5 100644 --- a/distrobuilder/main_lxd.go +++ b/distrobuilder/main_lxd.go @@ -2,10 +2,12 @@ package main import ( "fmt" + "net/url" "os" "os/exec" "path/filepath" + "github.com/google/uuid" lxd "github.com/lxc/lxd/shared" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -113,6 +115,11 @@ func (c *cmdLXD) commandPack() *cobra.Command { } func (c *cmdLXD) runPack(cmd *cobra.Command, args []string, overlayDir string) error { + // Return here as we cannot use chroot in Windows. + if c.global.definition.Source.Downloader == "windows" { + return nil + } + // Setup the mounts and chroot into the rootfs exitChroot, err := shared.SetupChroot(overlayDir, c.global.definition.Environment, nil) if err != nil { @@ -255,13 +262,110 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error if err != nil { return errors.Wrap(err, "Failed to mount root filesystem") } - defer lxd.RunCommand("umount", "-R", vmDir) + defer shared.RunCommand("umount", "-R", vmDir) err = vm.mountUEFIFilesystem() if err != nil { return errors.Wrap(err, "Failed to mount UEFI filesystem") } + // Copy EFI files and BCD to EFI partition, and populate MSR partition. + if targetOS == OSWindows { + // This is just a temporary mountpoint which will be removed later. + targetEFIDir := filepath.Join(vmDir, "boot", "efi") + targetEFIMicrosoftBootDir := filepath.Join(targetEFIDir, "EFI", "Microsoft", "Boot") + targetEFIBootDir := filepath.Join(targetEFIDir, "EFI", "Boot") + + err = os.MkdirAll(targetEFIMicrosoftBootDir, 0755) + if err != nil { + return errors.Wrapf(err, "Failed to create %s", targetEFIMicrosoftBootDir) + } + + err = os.MkdirAll(targetEFIBootDir, 0755) + if err != nil { + return errors.Wrapf(err, "Failed to create %s", targetEFIBootDir) + } + + // Copy EFI directory to boot partition + err = shared.RunCommand("rsync", "-a", "-HA", "--sparse", "--devices", "--checksum", "--numeric-ids", filepath.Join(overlayDir, "Windows", "Boot", "EFI")+"/", targetEFIMicrosoftBootDir) + if err != nil { + return errors.Wrap(err, "Failed to copy EFI data") + } + + // Copy bootx64.efi file + err = shared.RunCommand("rsync", "-a", "-HA", "--sparse", "--devices", "--checksum", "--numeric-ids", filepath.Join(targetEFIMicrosoftBootDir, "bootmgfw.efi"), filepath.Join(targetEFIBootDir, "bootx64.efi")) + if err != nil { + return errors.Wrap(err, "Failed to copy bootx64.efi") + } + + // Copy BCD file + bcdFile, err := url.Parse(c.global.definition.Targets.LXD.VM.BCD) + if err != nil { + return errors.Wrap(err, "Failed to get BCD file") + } + + if bcdFile.Scheme == "file" { + err = shared.RunCommand("rsync", "-a", "-HA", "--sparse", "--devices", "--checksum", "--numeric-ids", bcdFile.Path, filepath.Join(targetEFIMicrosoftBootDir, "BCD")) + if err != nil { + return errors.Wrap(err, "Failed to copy BCD file") + } + } + + // Fix UUIDs in the BCD file + efiPartUUID, err := vm.getUEFIPartitionUUID() + if err != nil { + return errors.Wrap(err, "Failed to get part UUID of EFI partition") + } + + dataPartUUID, err := vm.getRootfsPartitionUUID() + if err != nil { + return errors.Wrap(err, "Failed to get part UUID of rootfs partition") + } + + diskUUID, err := vm.getDiskUUID() + if err != nil { + return errors.Wrap(err, "Failed to get disk UUID") + } + + err = replaceUUID(filepath.Join(targetEFIMicrosoftBootDir, "BCD"), uuid.MustParse(c.global.definition.Targets.LXD.VM.UUID.EFI), uuid.MustParse(efiPartUUID)) + if err != nil { + return errors.Wrap(err, "Failed to replace EFI part UUID") + } + + err = replaceUUID(filepath.Join(targetEFIMicrosoftBootDir, "BCD"), uuid.MustParse(c.global.definition.Targets.LXD.VM.UUID.Data), uuid.MustParse(dataPartUUID)) + if err != nil { + return errors.Wrap(err, "Failed to replace rootfs part UUID") + } + + err = replaceUUID(filepath.Join(targetEFIMicrosoftBootDir, "BCD"), uuid.MustParse(c.global.definition.Targets.LXD.VM.UUID.Disk), uuid.MustParse(diskUUID)) + if err != nil { + return errors.Wrap(err, "Failed to replace rootfs part UUID") + } + + err = shared.RunCommand("umount", "-R", targetEFIDir) + if err != nil { + return errors.Wrap(err, "Failed to unmount UEFI filesystem") + } + + err = os.Remove(targetEFIDir) + if err != nil { + return errors.Wrap(err, "Failed to remove EFI mountpoint") + } + + // Copy MSR (Microsoft Reserved Partition) + msrFile, err := url.Parse(c.global.definition.Targets.LXD.VM.MSR) + if err != nil { + return errors.Wrap(err, "Failed to get MSR file") + } + + if msrFile.Scheme == "file" { + err = shared.RunCommand("dd", fmt.Sprintf("if=%s", msrFile.Path), fmt.Sprintf("of=%s", vm.getMSRDevFile())) + if err != nil { + return errors.Wrap(err, "Failed to copy MSR") + } + } + } + // We cannot use LXD's rsync package as that uses the --delete flag which // causes an issue due to the boot/efi directory being present. err = shared.RunCommand("rsync", "-a", "-HA", "--sparse", "--devices", "--checksum", "--numeric-ids", overlayDir+"/", vmDir) @@ -298,26 +402,28 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error } } - exitChroot, err := shared.SetupChroot(rootfsDir, - c.global.definition.Environment, mounts) - if err != nil { - return errors.Wrap(err, "Failed to chroot") - } - - // Run post files hook - for _, action := range c.global.definition.GetRunnableActions("post-files", imageTargets) { - err := shared.RunScript(action.Action) + if targetOS == OSLinux { + exitChroot, err := shared.SetupChroot(rootfsDir, + c.global.definition.Environment, mounts) if err != nil { - exitChroot() - return errors.Wrap(err, "Failed to run post-files") + return errors.Wrap(err, "Failed to chroot") + } + + // Run post files hook + for _, action := range c.global.definition.GetRunnableActions("post-files", imageTargets) { + err := shared.RunScript(action.Action) + if err != nil { + exitChroot() + return errors.Wrap(err, "Failed to run post-files") + } } - } - exitChroot() + exitChroot() + } // Unmount VM directory and loop device before creating the image. if c.flagVM { - _, err := lxd.RunCommand("umount", "-R", vmDir) + err := shared.RunCommand("umount", "-R", vmDir) if err != nil { return err } @@ -328,7 +434,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error } } - err = img.Build(c.flagType == "unified", c.flagCompression, c.flagVM) + err := img.Build(c.flagType == "unified", c.flagCompression, c.flagVM) if err != nil { return errors.Wrap(err, "Failed to create LXD image") } @@ -337,7 +443,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error } func (c *cmdLXD) checkVMDependencies() error { - dependencies := []string{"btrfs", "mkfs.ext4", "mkfs.vfat", "qemu-img", "rsync", "sgdisk"} + dependencies := []string{"btrfs", "mkfs.ext4", "mkfs.ntfs", "mkfs.vfat", "qemu-img", "rsync", "sgdisk"} for _, dep := range dependencies { _, err := exec.LookPath(dep) diff --git a/distrobuilder/vm.go b/distrobuilder/vm.go index 15049c6..827c8f4 100644 --- a/distrobuilder/vm.go +++ b/distrobuilder/vm.go @@ -70,7 +70,15 @@ func (v *vm) getRootfsDevFile() string { return "" } - return fmt.Sprintf("%sp2", v.loopDevice) + var partNum int + + if v.os == OSLinux { + partNum = 2 + } else { + partNum = 3 + } + + return fmt.Sprintf("%sp%d", v.loopDevice, partNum) } func (v *vm) getUEFIDevFile() string { @@ -81,6 +89,18 @@ func (v *vm) getUEFIDevFile() string { return fmt.Sprintf("%sp1", v.loopDevice) } +func (v *vm) getMSRDevFile() string { + if v.loopDevice == "" { + return "" + } + + if v.os != OSWindows { + return "" + } + + return fmt.Sprintf("%sp2", v.loopDevice) +} + func (v *vm) createEmptyDiskImage() error { f, err := os.Create(v.imageFile) if err != nil { @@ -104,8 +124,20 @@ func (v *vm) createEmptyDiskImage() error { func (v *vm) createPartitions() error { args := [][]string{ {"--zap-all"}, - {"--new=1::+100M", "-t 1:EF00"}, - {"--new=2::", "-t 2:8300"}, + // EFI system partition (ESP) + {"--new=1::+100M", "-t 1:C12A7328-F81F-11D2-BA4B-00A0C93EC93B"}, + } + + if v.os == OSLinux { + // Linux partition + args = append(args, []string{"--new=2::", "-t 2:0FC63DAF-8483-4772-8E79-3D69D8477DE4"}) + } else { + args = append(args, + // Microsoft Reserved Partition (MSR) + []string{"--new=2::+128M", "-t 2:E3C9E316-0B5C-4DB8-817D-F92DF00215AE"}, + /// Microsoft basic data partition + []string{"--new=3::", "-t 3:EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"}, + ) } for _, cmd := range args { @@ -163,7 +195,15 @@ func (v *vm) mountImage() error { } if !lxd.PathExists(v.getRootfsDevFile()) { - fields := strings.Split(deviceNumbers[2], ":") + var idx int + + if v.os == OSLinux { + idx = 2 + } else { + idx = 3 + } + + fields := strings.Split(deviceNumbers[idx], ":") major, err := strconv.Atoi(fields[0]) if err != nil { @@ -197,18 +237,15 @@ func (v *vm) umountImage() error { return err } - // Make sure that p1 and p2 are also removed. - if lxd.PathExists(v.getUEFIDevFile()) { - err := os.Remove(v.getUEFIDevFile()) - if err != nil { - return err - } - } + // Make sure that all partitions are removed. + for i := 1; i <= 3; i++ { + partition := fmt.Sprintf("%sp%d", v.loopDevice, i) - if lxd.PathExists(v.getRootfsDevFile()) { - err := os.Remove(v.getRootfsDevFile()) - if err != nil { - return err + if lxd.PathExists(partition) { + err := os.Remove(partition) + if err != nil { + return err + } } } @@ -268,6 +305,8 @@ func (v *vm) createFilesystems() error { err = shared.RunCommand("btrfs", "subvolume", "create", fmt.Sprintf("%s/@", v.rootfsDir)) case "ext4": err = shared.RunCommand("mkfs.ext4", "-F", "-b", "4096", "-i 8192", "-m", "0", "-L", "rootfs", "-E", "resize=536870912", v.getRootfsDevFile()) + case "ntfs": + err = shared.RunCommand("mkfs.ntfs", "-f", "-L", "rootfs", v.getRootfsDevFile()) } if err != nil { return err @@ -288,6 +327,7 @@ func (v *vm) mountRootFilesystem() error { case "btrfs": err = shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir, "-o", "defaults,subvol=/@") case "ext4": + case "ntfs": err = shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir) }
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel