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
 }