// Copyright 2019 Mikhail Klementev. All rights reserved. // Use of this source code is governed by a AGPLv3 license // (or later) that can be found in the LICENSE file. package main import ( "errors" "fmt" "io" "io/ioutil" "net/http" "os" "os/exec" "os/user" "time" "code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/qemu" "github.com/rs/zerolog/log" ) type ImageCmd struct { List ImageListCmd `cmd:"" help:"list images"` Edit ImageEditCmd `cmd:"" help:"edit image"` } type ImageListCmd struct{} func (cmd *ImageListCmd) Run(g *Globals) (err error) { usr, err := user.Current() if err != nil { return } entries, err := os.ReadDir(usr.HomeDir + "/.out-of-tree/images/") if err != nil { return } for _, e := range entries { fmt.Println(e.Name()) } return } type ImageEditCmd struct { Name string `help:"image name" required:""` } func (cmd *ImageEditCmd) Run(g *Globals) (err error) { usr, err := user.Current() if err != nil { return } image := usr.HomeDir + "/.out-of-tree/images/" + cmd.Name if !exists(image) { fmt.Println("image does not exist") } kcfg, err := config.ReadKernelConfig(g.Config.Kernels) if err != nil { return } if len(kcfg.Kernels) == 0 { return errors.New("No kernels found") } ki := config.KernelInfo{} for _, k := range kcfg.Kernels { if k.RootFS == image { ki = k break } } kernel := qemu.Kernel{ KernelPath: ki.KernelPath, InitrdPath: ki.InitrdPath, } q, err := qemu.NewSystem(qemu.X86x64, kernel, ki.RootFS) q.Mutable = true err = q.Start() if err != nil { fmt.Println("Qemu start error:", err) return } defer q.Stop() fmt.Print("ssh command:\n\n\t") fmt.Println(q.GetSSHCommand()) fmt.Print("\npress enter to stop") fmt.Scanln() q.Command("root", "poweroff") for !q.Died { time.Sleep(time.Second) } return } // inspired by Edd Turtle code func downloadFile(filepath string, url string) (err error) { out, err := os.Create(filepath) if err != nil { return } defer out.Close() resp, err := http.Get(url) if err != nil { return } defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: break case http.StatusForbidden, http.StatusNotFound: err = fmt.Errorf("Cannot download %s. It looks like you need "+ "to generate it manually and place it "+ "to ~/.out-of-tree/images/. "+ "Check documentation for additional information.", url) return default: err = fmt.Errorf("Something weird happens while "+ "download file: %d", resp.StatusCode) return } _, err = io.Copy(out, resp.Body) return } func unpackTar(archive, destination string) (err error) { // NOTE: If you're change anything in tar command please check also // BSD tar (or if you're using macOS, do not forget to check GNU Tar) // Also make sure that sparse files are extracting correctly cmd := exec.Command("tar", "-Sxf", archive) cmd.Dir = destination + "/" log.Debug().Msgf("%v", cmd) rawOutput, err := cmd.CombinedOutput() if err != nil { err = fmt.Errorf("%v: %s", err, rawOutput) return } return } func downloadImage(path, file string) (err error) { tmp, err := ioutil.TempDir(tempDirBase, "out-of-tree_") if err != nil { return } defer os.RemoveAll(tmp) archive := tmp + "/" + file + ".tar.gz" url := imagesBaseURL + file + ".tar.gz" err = downloadFile(archive, url) if err != nil { return } err = unpackTar(archive, path) return }