diff --git a/src/exec/QemuImg.go b/src/exec/QemuImg.go index fc4cf9832e8805843e8906396878a817cc767f86..76c346833292aed7092c0aac1f39b50aef7f1048 100644 --- a/src/exec/QemuImg.go +++ b/src/exec/QemuImg.go @@ -1,10 +1,12 @@ package exec import ( + "fmt" "os/exec" "strings" "errors" "strconv" + "nexus-server/logger" ) const ( @@ -12,6 +14,8 @@ const ( qemuimgBinaryMinVersion = 4 ) +var log = logger.GetInstance() + // Check qemu-img is available and the version is recent enough. func CheckQemuImg() error { output, err := exec.Command(qemuimgBinary, "--version").Output() @@ -36,11 +40,25 @@ func CheckQemuImg() error { } // Creates a qemu-img command that creates a disk image (VM) baked by a base image (template). -func NewQemuImgCreate(templateDiskFile string, vmDiskFile string) *exec.Cmd { - return exec.Command(qemuimgBinary, "create", "-F", "qcow2", "-b", templateDiskFile, "-f", "qcow2", vmDiskFile) +func QemuImgCreate(templateDiskFile, vmDiskFile string) error { + cmd := exec.Command(qemuimgBinary, "create", "-F", "qcow2", "-b", templateDiskFile, "-f", "qcow2", vmDiskFile) + stdoutStderr, err := cmd.CombinedOutput() + if err != nil { + output := fmt.Sprintf("[%s]", stdoutStderr) + log.Error("Failed creating VM disk image: "+output) + return errors.New("Failed creating VM disk image") + } + return nil } // Creates a qemu-img command that rebase an overlay image in order to become a standalone image. -func NewQemuImgRebase(overlayFile string) *exec.Cmd { - return exec.Command(qemuimgBinary, "rebase", "-b", "", overlayFile) +func QemuImgRebase(overlayFile string) error { + cmd := exec.Command(qemuimgBinary, "rebase", "-b", "", overlayFile) + stdoutStderr, err := cmd.CombinedOutput() + if err != nil { + output := fmt.Sprintf("[%s]", stdoutStderr) + log.Error("Failed rebasing template disk from VM: "+": "+output) + return errors.New("Failed rebasing template disk from VM") + } + return nil } diff --git a/src/exec/QemuSystem.go b/src/exec/QemuSystem.go index 4aa5e2840a7022b2338f8cdeb25cf6fded0b71fd..aae61a3c64d9827f77dae24da80ba392093fca67 100644 --- a/src/exec/QemuSystem.go +++ b/src/exec/QemuSystem.go @@ -38,7 +38,7 @@ func CheckQemuSystem() error { // Creates a qemu-system command. // The nic argument must be either "none" or "user". -func NewQemuSystem(qgaSock string, cpus int, ram int, nic string, diskFile string, spicePort int, secretPwdFile string, certDir string) (*exec.Cmd, error) { +func NewQemuSystem(qgaSock string, cpus, ram int, nic, diskFile string, spicePort int, secretPwdFile, certDir string) (*exec.Cmd, error) { nicArgs := "none" if nic == "user" { addr, err := utils.RandMacAddress() diff --git a/src/exec/VirtCopyOut.go b/src/exec/VirtCopyOut.go index 4126fe6fbe97c1155e8be99f948f4bbb83331e4f..cf4a89834749b92f1d63a2ce3b9836ab0b5b8099 100644 --- a/src/exec/VirtCopyOut.go +++ b/src/exec/VirtCopyOut.go @@ -1,6 +1,7 @@ package exec import ( + "fmt" "os/exec" "strings" "errors" @@ -29,9 +30,17 @@ func CheckVirtCopyOut() error { return nil } -// Creates a virt-copy-out command that recursively extracts (i.e. copy out) a directory from a VM's disk. -// Note: localDir must already exist, otherwise the command will fail. -func NewVirtCopyOut(vmDiskFile string, vmDir string, localDir string) *exec.Cmd { - // mkdir tmp && virt-copy-out -a disk.qcow /home tmp - return exec.Command(virtcopyoutBinary, "-a", vmDiskFile, vmDir, localDir) +// Creates a virt-copy-out command that recursively copies a directory from the VM's +// filesystem (vmDir) into a directory on the host (localDir). +// Note: both directories must exist, otherwise the command will fail. +func CopyFromVM(vmDiskFile, vmDir, localDir string) error { + cmd := exec.Command(virtcopyoutBinary, "-a", vmDiskFile, vmDir, localDir) + stdoutStderr, err := cmd.CombinedOutput() + if err != nil { + output := fmt.Sprintf("[%s]", stdoutStderr) + msg := "Failed reading \""+vmDir+"\" in qcow ("+vmDiskFile+"): "+output + log.Error(msg) + return errors.New(msg) + } + return nil } diff --git a/src/nexus-server.go b/src/nexus-server.go index 4ff8759f5a8ae3ffb9ae87da1b1071dded538d6d..7634593e63aed9b7e1fdac0215752f2534e610ea 100644 --- a/src/nexus-server.go +++ b/src/nexus-server.go @@ -37,6 +37,9 @@ func main() { if err := exec.CheckVirtCopyOut(); err != nil { log.Fatal(err) } + if err := exec.CheckVirtCopyIn(); err != nil { + log.Fatal(err) + } usage := func() { fmt.Println("Usage of "+path.Base(os.Args[0])+":") diff --git a/src/router/routerVMs.go b/src/router/routerVMs.go index be6dd7449314b6066a8bd801b51edefb06982456..f8fa7e43f4550ae95a9728f8c3d784edf2df5c88 100644 --- a/src/router/routerVMs.go +++ b/src/router/routerVMs.go @@ -3,7 +3,6 @@ package router import ( "io" "os" - "fmt" "net/http" "path/filepath" "nexus-server/vms" @@ -11,7 +10,6 @@ import ( "nexus-server/users" "nexus-server/paths" "nexus-server/utils" - "nexus-server/exec" "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/go-playground/validator/v10" @@ -355,7 +353,7 @@ func (r *RouterVMs)DeleteVMAccessForUser(c echo.Context) error { // - the logged user has the userCapabilityAny capability. // - the VM access for the logged user matches the vmAccessCapability capability. // Also, VMs for which cond is false are filtered out. -func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny string, vmAccessCapability string, cond vms.VMKeeperFn) error { +func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny, vmAccessCapability string, cond vms.VMKeeperFn) error { // Retrieves logged user from context. user, err := getLoggedUser(r.users, c) if err != nil { @@ -384,7 +382,7 @@ func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny string, vmAc // Helper function that performs an action on a VM based either on: // - the logged user has the userCapabilityAny capability. // - the VM access for the logged user matches the vmAccessCapability capability. -func (r *RouterVMs)performVMAction(c echo.Context, userCapabilityAny string, vmAccessCapability string, action vmActionFn) error { +func (r *RouterVMs)performVMAction(c echo.Context, userCapabilityAny, vmAccessCapability string, action vmActionFn) error { // Retrieves the VM on which to perform the action. id, err := uuid.Parse(c.Param("id")) if err != nil { @@ -449,13 +447,8 @@ func (r *RouterVMs)ExportVMDir(c echo.Context) error { } }(uniqTmpDir) - // Extracts the VM's directory inside the temporary directory above. - vmDisk := vm.GetDiskPath() - cmd := exec.NewVirtCopyOut(vmDisk, p.Dir, uniqTmpDir) - stdoutStderr, err := cmd.CombinedOutput() - if err != nil { - output := fmt.Sprintf("[%s]", stdoutStderr) - log.Error("Failed reading \""+p.Dir+"\" in qcow "+vmDisk+": "+output) + // Extracts VM's p.Dir directory inside uniqTmpDir directory on the host. + if err := r.vms.ExportVMFiles(vm, p.Dir, uniqTmpDir); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Failed reading VM's dir") } @@ -483,11 +476,10 @@ func (r *RouterVMs)ExportVMDir(c echo.Context) error { // curl --cacert ca.pem -X POST https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/importfiles -H 'Content-Type: multipart/form-data' -F dir="/home/nexus" -F file=@files.tar -H "Authorization: Bearer <AccessToken>" func (r *RouterVMs)ImportFilesToVM(c echo.Context) error { return r.performVMAction(c, caps.CAP_VM_WRITEFS_ANY, caps.CAP_VM_WRITEFS, func(c echo.Context, vm *vms.VM) error { - // Retrieve "dir" argument - dir := c.FormValue("dir") - log.Info("ImportFilesToVM: dir=",dir) + // Retrieve the "dir" argument + vmDir := c.FormValue("dir") - // Retrieve the tar archive: uploadedFile + // Retrieve the tar archive (uploadedTarFile) tarFile, err := c.FormFile("file") if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) @@ -505,22 +497,37 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error { log.Error("Failed creating random UUID: "+err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - uploadedFile := filepath.Join(tmpDir, "upload_"+uuid.String()+".tar") - dst, err := os.Create(uploadedFile) + uploadedTarFile := filepath.Join(tmpDir, "upload_"+uuid.String()+".tar") + dst, err := os.Create(uploadedTarFile) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } defer dst.Close() - //defer os.Remove(uploadedFile) + //defer os.Remove(uploadedTarFile) if _, err = io.Copy(dst, src); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - // Extract the archive + // Extract the archive into tmpExtractDir + tmpExtractDir := filepath.Join(tmpDir, "extract_"+uuid.String()) + if err := os.Mkdir(tmpExtractDir, 0750); err != nil { + msg := "ImportFilesToVM: failed creating dir: "+err.Error() + log.Error(msg) + return echo.NewHTTPError(http.StatusInternalServerError, msg) + } + if err := utils.Untar(uploadedTarFile, tmpExtractDir); err != nil { + msg := "ImportFilesToVM: failed unarchiving tarball: "+err.Error() + log.Error(msg) + return echo.NewHTTPError(http.StatusInternalServerError, msg) + } + // Copy the archive's files into the VM // IMPORTANT: check the VM is NOT running! + if err = r.vms.ImportFilesToVM(vm, tmpExtractDir, vmDir); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } // Remove the files and the archive diff --git a/src/utils/utils.go b/src/utils/utils.go index 53429142f6ff8f10d8c6f3f9c0a3268190fbccd0..24392a5b9c1bd3d4839159f49c0427d4bd45728c 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -50,11 +50,11 @@ func RandInit() { } // Returns an int in the range [min,max] (both inclusive). -func Rand(min int, max int) int { +func Rand(min, max int) int { return rand.Intn(max-min+1)+min } -func CopyFiles(source string, dest string) error { +func CopyFiles(source, dest string) error { src, err := os.Open(source) if err != nil { return err @@ -115,10 +115,11 @@ func IsPortAvailable(port int) bool { return true } -// Creates the destFile .tar archive of srcDir and all its files and subdirectories. -// This implementation fails if a symlink points to an absolute path that doesn't exist on the host -func TarDir(srcDir, destFile string) error { - tarFile, err := os.Create(destFile) +// Creates the destTarball .tar archive of srcDir and all its files and subdirectories. +// This implementation fails if a symlink points to an absolute path that doesn't exist on the host. +// Source code taken from: https://golangdocs.com/tar-gzip-in-golang +func TarDir(srcDir, destTarball string) error { + tarFile, err := os.Create(destTarball) if err != nil { return err } @@ -174,3 +175,61 @@ func TarDir(srcDir, destFile string) error { return err }) } + +// Unarchive srcTarball into destDir. +func Untar(srcTarball, destDir string) error { + reader, err := os.Open(srcTarball) + if err != nil { + return err + } + defer reader.Close() + + // First pass: reads and creates directories first. + tarDirReader := tar.NewReader(reader) + for { + header, err := tarDirReader.Next() + if err == io.EOF { + break + } else if err != nil { + return err + } + path := filepath.Join(destDir, header.Name) + info := header.FileInfo() + if info.IsDir() { + if err = os.MkdirAll(path, info.Mode()); err != nil { + return err + } + continue + } + } + + // Seeks to the beginning of the file. + whence := 0 + reader.Seek(0, whence) + + // Second pass: reads and creates files. + tarFileReader := tar.NewReader(reader) + for { + header, err := tarFileReader.Next() + if err == io.EOF { + break + } else if err != nil { + return err + } + path := filepath.Join(destDir, header.Name) + info := header.FileInfo() + if !info.IsDir() { + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode()) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(file, tarFileReader) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/src/vms/template.go b/src/vms/template.go index 688141adaaa87fa39b03bb922528ff577d62cd7f..e50f35b53cc9cd5adf91b2667e8d0d76a340264e 100644 --- a/src/vms/template.go +++ b/src/vms/template.go @@ -2,7 +2,6 @@ package vms import( "os" - "fmt" "errors" "path/filepath" "encoding/json" @@ -30,7 +29,7 @@ const( var dummyTemplate = Template{} // Creates a template from a VM's disk. -func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Template, error) { +func NewTemplateFromVM(name, owner, access string, vm *VM) (*Template, error) { // Marks the VM to copy from as being busy. if err := vms.setDiskBusy(vm); err != nil { return nil, errors.New("Failed setting disk busy flag during template creation: "+err.Error()) @@ -47,19 +46,17 @@ func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Templ // Creates the template directory. templatesDir := GetTemplatesInstance().getDir() templateDir := filepath.Join(templatesDir, template.ID.String()) - // log.Info("(0) create tpl dir: "+templateDir) if err := os.Mkdir(templateDir, 0750); err != nil { - log.Error("Failed creating template dir: "+err.Error()) - return nil, errors.New("Failed creating template dir: "+err.Error()) + msg := "Failed creating template dir: "+err.Error() + log.Error(msg) + return nil, errors.New(msg) } // 1) Copy VM's overlay file into a new one (in the VM's directory!): // cp disk.qcow disk.tpl.qcow vmDiskFile, _ := filepath.Abs(filepath.Join(vm.dir, vmDiskFile)) tplDiskFile, _ := filepath.Abs(filepath.Join(vm.dir, "/disk.tpl.qcow")) - // log.Info("(1) copy: "+vmDiskFile+" to "+tplDiskFile) - err = utils.CopyFiles(vmDiskFile, tplDiskFile) - if err != nil { + if err := utils.CopyFiles(vmDiskFile, tplDiskFile); err != nil { template.delete() GetVMsInstance().updateVM(vm) log.Error("Failed copying VM overlay disk: "+err.Error()) @@ -68,26 +65,17 @@ func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Templ // 2) Rebase the new template overlay in order to become a standalone disk file: // qemu-img rebase -b "" disk.tpl.qcow - cmd := exec.NewQemuImgRebase(tplDiskFile) - // log.Info("(2) rebase: "+cmd.String()) - stdoutStderr, err := cmd.CombinedOutput() - if err != nil { - log.Debug("(2) rebase: "+cmd.String()) + if err := exec.QemuImgRebase(tplDiskFile); err != nil { template.delete() os.Remove(tplDiskFile) GetVMsInstance().updateVM(vm) - output := fmt.Sprintf("[%s]", stdoutStderr) - log.Error("Failed rebasing template disk from VM: "+": "+output) - return nil, errors.New("Failed rebasing template disk from VM") + return nil, err } // 3) Move the template disk file into the template directory: // mv disk.tpl.qcow new_template_dir/ destFile, _ := filepath.Abs(filepath.Join(templateDir, templateDiskFile)) - // log.Info("(3) move "+tplDiskFile+" to "+destFile) - err = os.Rename(tplDiskFile, destFile) - if err != nil { - log.Debug("(3) move "+tplDiskFile+" -> "+destFile) + if err := os.Rename(tplDiskFile, destFile); err != nil { template.delete() os.Remove(tplDiskFile) GetVMsInstance().updateVM(vm) @@ -104,7 +92,7 @@ func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Templ } // Creates a template from a qcow file. -func NewTemplateFromQCOW(name string, owner string, access string, qcowFile string) (*Template, error) { +func NewTemplateFromQCOW(name, owner, access, qcowFile string) (*Template, error) { // Creates the template. template, err := newTemplate(name, owner, access) if err != nil { @@ -150,7 +138,7 @@ func newEmptyTemplate() *Template { } // Creates a template. -func newTemplate(name string, owner string, access string) (*Template, error) { +func newTemplate(name, owner, access string) (*Template, error) { id, err := uuid.NewRandom() if err != nil { log.Error("Failed creating template: "+err.Error()) diff --git a/src/vms/vm.go b/src/vms/vm.go index 395514a3676d8277e61273394f17a89e90e709d1..45250eb94c71d96aa5068552e646d39aa24c8a32 100644 --- a/src/vms/vm.go +++ b/src/vms/vm.go @@ -2,7 +2,6 @@ package vms import ( "os" - "fmt" "sync" "path" "errors" @@ -31,10 +30,11 @@ type ( TemplateID uuid.UUID `json:"templateID" validate:"required"` Access map[string]caps.Capabilities `json:"access" validate:"required"` + // None of the fields below are serialized to disk. dir string // VM directory qgaSock string // QEMU Guest Agent (QGA) UNIX socket Run runStates - DiskBusy bool // If true the VM's disk is busy (cannot be modified or deleted). + DiskBusy bool // If true the VM's disk is busy (cannot be modified or deleted) mutex *sync.Mutex } @@ -67,7 +67,7 @@ const ( var dummyVM = VM{} // Creates a VM. -func NewVM(creatorEmail string, caps caps.Capabilities, name string, cpus int, ram int, nic NicType, templateID uuid.UUID) (*VM, error) { +func NewVM(creatorEmail string, caps caps.Capabilities, name string, cpus, ram int, nic NicType, templateID uuid.UUID) (*VM, error) { vmID, err := uuid.NewRandom() if err != nil { log.Error("Failed creating VM: "+err.Error()) @@ -92,7 +92,7 @@ func NewVM(creatorEmail string, caps caps.Capabilities, name string, cpus int, r return vm, nil } -func (vm *VM)GetDiskPath() string { +func (vm *VM)getDiskPath() string { vmDiskFile, _ := filepath.Abs(filepath.Join(vm.dir, vmDiskFile)) return vmDiskFile } @@ -200,13 +200,10 @@ func (vm *VM)writeFiles() error { // Creates vmDiskFile as an overlay on top of the template disk. // NOTE: template and output file must be both specified as absolute paths. templateDiskFile, _ := filepath.Abs(filepath.Join(GetTemplatesInstance().getDir(), template.ID.String(), templateDiskFile)) - cmd := exec.NewQemuImgCreate(templateDiskFile, vm.GetDiskPath()) - stdoutStderr, err := cmd.CombinedOutput() - if err != nil { + + if err := exec.QemuImgCreate(templateDiskFile, vm.getDiskPath()); err != nil { vm.delete() - output := fmt.Sprintf("[%s]", stdoutStderr) - log.Error("Failed creating VM disk image: "+output) - return errors.New("Failed creating VM disk image") + return err } return nil @@ -329,10 +326,6 @@ func (vm *VM)stop() error { return errors.New("Failed stopping VM: VM is not running") } - if vm.DiskBusy { - return errors.New("Failed stopping VM: VM disk is busy") - } - // Sends a SIGINT signal to terminate the QEMU process. // Note that QEMU terminates with status code 0 in this case (i.e. no error). if err := syscall.Kill(vm.Run.Pid, syscall.SIGINT); err != nil { @@ -344,8 +337,8 @@ func (vm *VM)stop() error { } // Gracefully stops a running VM. -// Uses QMP commands to talk to the VM, which means QEMU Guest Agent must be running in the VM -// otherwise the VM won't shutdown. +// Uses QGA commands to talk to the VM, which means QEMU Guest Agent must be running +// in the VM, otherwise the VM won't shutdown. func (vm *VM)shutdown() error { if !vm.IsRunning() { return errors.New("Shutdown failed: VM is not running") @@ -355,7 +348,7 @@ func (vm *VM)shutdown() error { return errors.New("Shutdown failed: VM disk is busy") } - // Sends a QMP command to order the VM to shutdown. + // Sends a QGA command to order the VM to shutdown. con := qga.New() if err := con.Open(vm.qgaSock); err != nil { log.Error("VM shutdown failed (open): "+err.Error()) @@ -377,7 +370,7 @@ func (vm *VM)shutdown() error { } // Executes the VM in QEMU using the specified spice port and password. -func (vm *VM)runQEMU(port int, pwd string, pwdFile string, endofExecFn endOfExecCallback) error { +func (vm *VM)runQEMU(port int, pwd, pwdFile string, endofExecFn endOfExecCallback) error { pkiDir := paths.GetInstance().NexusPkiDir cmd, err := exec.NewQemuSystem(vm.qgaSock, vm.Cpus, vm.Ram, string(vm.Nic), filepath.Join(vm.dir, vmDiskFile), port, pwdFile, pkiDir) if err != nil { diff --git a/src/vms/vms.go b/src/vms/vms.go index cc0ae71fd50fdf3b4413a440257bdd0429d8cc44..1b0ada10ff05515a654082551a7cbd78a1c9279a 100644 --- a/src/vms/vms.go +++ b/src/vms/vms.go @@ -6,6 +6,7 @@ import ( "sync" "errors" "path/filepath" + "nexus-server/exec" "nexus-server/caps" "nexus-server/paths" "nexus-server/utils" @@ -259,7 +260,7 @@ func (vms *VMs)IsTemplateUsed(templateID string) bool { } // Edit a VM' specs: name, cpus, ram, nic -func (vms *VMs)EditVM(vmID uuid.UUID, name string, cpus int, ram int, nic NicType) error { +func (vms *VMs)EditVM(vmID uuid.UUID, name string, cpus, ram int, nic NicType) error { // Retrieves the VM to be edited. vm, err := vms.GetVM(vmID) if err != nil { @@ -299,7 +300,7 @@ func (vms *VMs)EditVM(vmID uuid.UUID, name string, cpus int, ram int, nic NicTyp // Set a VM's Access for a given user (email). // loggedUserEmail is the email of the currently logged user // userMail is the email of the user for which to modify the access -func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail string, newAccess caps.Capabilities) error { +func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string, newAccess caps.Capabilities) error { if !caps.AreVMAccessCapsValid(newAccess) { return errors.New("Invalid capability") } @@ -334,7 +335,7 @@ func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail str // Remove a VM's Access for a given user (email). // loggedUserEmail is the email of the currently logged user // userMail is the email of the user for which to remove the access -func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail string) error { +func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string) error { // Retrieves the VM for which the access caps must be changed. vm, err := vms.GetVM(vmID) if err != nil { @@ -363,6 +364,25 @@ func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail return nil } +// Exports a VM's directory and its subdirectories into the specified directory on the host. +func (vms *VMs)ExportVMFiles(vm *VM, vmDir, hostDir string) error { + vmDisk := vm.getDiskPath() + return exec.CopyFromVM(vmDisk, vmDir, hostDir) +} + +// Import files into a VM's filesystem, in a specified directory. +func (vms *VMs)ImportFilesToVM(vm *VM, hostDir, vmDir string) error { + // Marks the VM to copy from as being busy. + if err := vms.setDiskBusy(vm); err != nil { + return errors.New("Failed setting disk busy flag during VM files import: "+err.Error()) + } + // Clears the VM from being busy. + defer vms.clearDiskBusy(vm) + + vmDisk := vm.getDiskPath() + return exec.CopyToVM(vmDisk, hostDir, vmDir) +} + // Marks a VM as "busy", meaning its disk file is being accessed for a possibly long time. func (vms *VMs)setDiskBusy(vm *VM) error { vm.mutex.Lock() @@ -377,7 +397,7 @@ func (vms *VMs)setDiskBusy(vm *VM) error { vms.rwlock.Lock() defer vms.rwlock.Unlock() - vms.updateVM(vm) + vms.updateVMMap(vm) return nil } @@ -395,7 +415,7 @@ func (vms *VMs)clearDiskBusy(vm *VM) error { vms.rwlock.Lock() defer vms.rwlock.Unlock() - vms.updateVM(vm) + vms.updateVMMap(vm) return nil }