diff --git a/src/server/router/routerTemplates.go b/src/server/router/routerTemplates.go
index 8679d72b2976c36ba893b784850f2c4c052c0181..4535628a55299831f3b816aae806f297389b0306 100644
--- a/src/server/router/routerTemplates.go
+++ b/src/server/router/routerTemplates.go
@@ -184,7 +184,7 @@ func (r *RouterTemplates)CreateTemplateFromQCOW(c echo.Context) error {
 // Requires either of:
 // CAP_TPL_DESTROY_ANY: any template can be deleted.
 // CAP_TPL_DESTROY: only a template owned by the user can be deleted.
-// Remark: a template can only be deleted if no VM references it!
+// REMARK: a template can only be deleted if no VM references it!
 // curl --cacert ca.pem -X DELETE https://localhost:1077/templates/4913a2bb-edfe-4dfe-af53-38197a44523b -H "Authorization: Bearer <AccessToken>"
 func (r *RouterTemplates)DeleteTemplate(c echo.Context) error {
     // Retrieves logged user from context.
diff --git a/src/server/router/routerVMs.go b/src/server/router/routerVMs.go
index ce098997bf44c9e46db7fe535e8a958719147055..4d9d2e34cf729385a96672f7824923bd04fba902 100644
--- a/src/server/router/routerVMs.go
+++ b/src/server/router/routerVMs.go
@@ -103,7 +103,7 @@ func (r *RouterVMs)GetRebootableVMs(c echo.Context) error {
 // curl --cacert ca.pem -X GET https://localhost:1077/vms/edit -H "Authorization: Bearer <AccessToken>"
 func (r *RouterVMs)GetEditableVMs(c echo.Context) error {
     return r.getFilteredVMs(c, caps.CAP_VM_EDIT_ANY, caps.CAP_VM_EDIT, func(vm *vms.VM) bool {
-        return !vm.IsRunning()
+        return true
     })
 }
 
@@ -148,7 +148,7 @@ func (r *RouterVMs)GetModifiableVMAccessVMs(c echo.Context) error {
     }
 
     filterFunc := func(vm *vms.VM) bool {
-        return isModifyVMAccessAllowed(user, vm) && !vm.IsRunning()
+        return isModifyVMAccessAllowed(user, vm)
     }
 
     return c.JSONPretty(http.StatusOK, r.vms.GetNetworkSerializedVMs(filterFunc), "    ")
@@ -161,7 +161,7 @@ func (r *RouterVMs)GetModifiableVMAccessVMs(c echo.Context) error {
 // curl --cacert ca.pem -X GET https://localhost:1077/vms/exportdir -H "Authorization: Bearer <AccessToken>"
 func (r *RouterVMs)GetDirExportableVMs(c echo.Context) error {
     return r.getFilteredVMs(c, caps.CAP_VM_READFS_ANY, caps.CAP_VM_READFS, func(vm *vms.VM) bool {
-        return true
+        return !vm.IsRunning()
     })
 }
 
@@ -172,7 +172,7 @@ func (r *RouterVMs)GetDirExportableVMs(c echo.Context) error {
 // curl --cacert ca.pem -X GET https://localhost:1077/vms/importfiles -H "Authorization: Bearer <AccessToken>"
 func (r *RouterVMs)GetFilesImportableVMs(c echo.Context) error {
     return r.getFilteredVMs(c, caps.CAP_VM_WRITEFS_ANY, caps.CAP_VM_WRITEFS, func(vm *vms.VM) bool {
-        return true
+        return !vm.IsRunning()
     })
 }
 
@@ -246,7 +246,6 @@ func (r *RouterVMs)StartVM(c echo.Context) error {
 // curl --cacert ca.pem -X PUT https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/startwithcreds -H "Authorization: Bearer <AccessToken>"
 func (r *RouterVMs)StartVMWithCreds(c echo.Context) error {
     return r.applyOnFilteredVMs(c, caps.CAP_VM_START_ANY, caps.CAP_VM_START, func(c echo.Context, vm *vms.VM) error {
-
         // Deserializes and validates client's parameters.
         p := new(params.VMStartWithCreds)
         if err := decodeJson(c, &p); err != nil {
@@ -551,4 +550,3 @@ func (r *RouterVMs)applyOnFilteredVMs(c echo.Context, userCapabilityAny, vmAcces
         return action(c, vm)
     }
 }
-
diff --git a/src/server/users/users.go b/src/server/users/users.go
index 396866e22a2e0e30ae520119af71038d96a24a22..35422def69d9fbe2addbfde03c5f48d5d394b117 100644
--- a/src/server/users/users.go
+++ b/src/server/users/users.go
@@ -33,7 +33,6 @@ func InitUsers() error {
 }
 
 // Returns all users.
-// Thread-safe fonction.
 func (users *Users)GetUsers() []User {
     users.rwlock.RLock()
     list := users.usersToList()
@@ -47,7 +46,6 @@ func (users *Users)GetUsers() []User {
 }
 
 // Returns a user by her email (unique).
-// Thread-safe fonction.
 func (users *Users)GetUserByEmail(email string) (User, error) {
     users.rwlock.RLock()
     defer users.rwlock.RUnlock()
@@ -62,8 +60,10 @@ func (users *Users)GetUserByEmail(email string) (User, error) {
 }
 
 // Deletes a user by its email and upates the user file.
-// TODO: only deletes a user when no VMs and templates are owned by her?
-// Thread-safe fonction.
+// REMARKS:
+// After a user is deleted, their VMs and templates remain.
+// Alternative: make sure a user's VMs and templates are first deleted before deleting a user.
+// I think the current behavior is preferable.
 func (users *Users)DeleteUserByEmail(email string) error {
     users.rwlock.Lock()
     defer users.rwlock.Unlock()
@@ -78,7 +78,6 @@ func (users *Users)DeleteUserByEmail(email string) error {
 }
 
 // Adds a user and updates the users file.
-// Thread-safe fonction.
 func (users *Users)AddUser(user *User) error {
     users.rwlock.Lock()
     defer users.rwlock.Unlock()
@@ -97,7 +96,6 @@ func (users *Users)AddUser(user *User) error {
 }
 
 // Updates a user and updates the user file.
-// Thread-safe fonction.
 func (users *Users)UpdateUser(user *User) error {
     users.rwlock.Lock()
     defer users.rwlock.Unlock()
diff --git a/src/server/version/version.go b/src/server/version/version.go
index 631aa4f429271a44c2af6b777c89587f663fa84a..70c2359597e4f7b5f8bc3a97ad43391397a2d8f4 100644
--- a/src/server/version/version.go
+++ b/src/server/version/version.go
@@ -7,7 +7,7 @@ import (
 const (
     major  = 1
     minor  = 8
-    bugfix = 4
+    bugfix = 5
 )
 
 type Version struct {
diff --git a/src/server/vms/template.go b/src/server/vms/template.go
index d07e29965af694d1e8244f05074799567280fd7e..01c80800b38cfcf18b043b49fda010795bf332e7 100644
--- a/src/server/vms/template.go
+++ b/src/server/vms/template.go
@@ -27,19 +27,20 @@ const (
     templatePrivate = "private"
 )
 
-// Creates a template from a VM's disk.
+// Creates a template from a VM's disk and returns a pointer to it.
+// Concurrency: safe
 func NewTemplateFromVM(name, owner, access string, vm *VM) (*Template, error) {
     vm.mutex.Lock()
 
     if vm.IsRunning() {
         vm.mutex.Unlock()
-        return nil, errors.New("Failed: VM must be stopped")
+        return nil, errors.New("Failed creating template: VM must be stopped")
     }
 
     // Marks VM as "busy", meaning its disk file is being accessed for a possibly long time.
-    if vm.DiskBusy {
+    if vm.IsDiskBusy() {
         vm.mutex.Unlock()
-        return nil, errors.New("Failed: VM disk must not be busy")
+        return nil, errors.New("Failed creating template: VM disk is in use (busy)")
     }
     vm.DiskBusy = true
 
@@ -97,7 +98,8 @@ func NewTemplateFromVM(name, owner, access string, vm *VM) (*Template, error) {
     return template, nil
 }
 
-// Creates a template from a qcow file.
+// Creates a template from a qcow file and returns a pointer to it.
+// Concurrency: safe
 func NewTemplateFromQCOW(name, owner, access, qcowFile string) (*Template, error) {
     // Creates the template.
     template, err := newTemplate(name, owner, access)
@@ -123,6 +125,9 @@ func NewTemplateFromQCOW(name, owner, access, qcowFile string) (*Template, error
     return template, nil
 }
 
+// Checks whether the specified access is valid.
+// Returns an error if it's not.
+// Concurrency: safe
 func ValidateTemplateAccess(access string) error {
     if access != templatePrivate && access != templatePublic {
         return errors.New("Wrong template access type")
@@ -130,25 +135,33 @@ func ValidateTemplateAccess(access string) error {
     return nil
 }
 
+// Returns true if the template is public.
+// Concurrency: safe
 func (template *Template)IsPublic() bool {
     return template.t.Access == templatePublic
 }
 
 // Returns the absolute path to the template's disk image.
+// Concurrency: safe
 func (template *Template)GetTemplateDiskPath() string {
     path, _ := filepath.Abs(filepath.Join(template.getTemplateDir(), templateDiskFile))
     return path
 }
 
+// Returns the template's owner.
+// Concurrency: safe
 func (template *Template)GetOwner() string {
     return template.t.Owner
 }
 
+// Returns a serialized version of the template.
+// Concurrency: safe
 func (template *Template)SerializeToNetwork() template.TemplateSerialized {
     return template.t
 }
 
-// Cr1eates a template.
+// Creates a template given the specified name, owner and access and returns a pointer to it.
+// Concurrency: safe
 func newTemplate(name, owner, access string) (*Template, error) {
     id, err := uuid.NewRandom()
     if err != nil {
@@ -165,7 +178,8 @@ func newTemplate(name, owner, access string) (*Template, error) {
     return template, nil
 }
 
-// Creates a template from a templateConfFile and returns it.
+// Creates a template from a template configuration file (json) and returns a pointer to it.
+// Concurrency: safe
 func newTemplateFromFile(file string) (*Template, error) {
     filein, err := os.OpenFile(file, os.O_RDONLY, 0)
     if err != nil {
@@ -190,7 +204,8 @@ func newTemplateFromFile(file string) (*Template, error) {
     return template, nil
 }
 
-// Writes a template's config file.
+// Writes a template's configuration file (json).
+// Concurrency: unsafe
 func (template *Template)writeConfig() error {
     templateDir := template.getTemplateDir()
     templateConfigFile := filepath.Join(templateDir, templateConfFile)
@@ -214,7 +229,8 @@ func (template *Template)writeConfig() error {
     return nil
 }
 
-// Deletes a template's directory and its content.
+// Deletes a template's directory along its content.
+// Concurrency: unsafe
 func (template *Template)delete() error {
     templateDir := template.getTemplateDir()
     if err := os.RemoveAll(templateDir); err != nil {
@@ -225,11 +241,15 @@ func (template *Template)delete() error {
     return nil
 }
 
+// Returns a template's directory.
+// Concurrency: safe
 func (template *Template)getTemplateDir() string {
     return filepath.Join(templates.dir, template.t.ID.String())
 }
 
 // Checks that the template's fields are valid.
+// Returns an error if they are not.
+// Concurrency: safe
 func (template *Template)validate() error {
     if err := validator.New().Struct(template.t); err != nil {
         return err
diff --git a/src/server/vms/templates.go b/src/server/vms/templates.go
index 3983c018727b9311ecdf0fa6d18a162dd300c2e9..843943b98568e364462c9f2f895dcdfa2634731f 100644
--- a/src/server/vms/templates.go
+++ b/src/server/vms/templates.go
@@ -25,14 +25,16 @@ type (
 
 var templates *Templates
 
-// Returns a Templates "singleton".
+// Returns a Templates singleton to access this module's public functions.
 // IMPORTANT: the InitTemplates function must have been previously called!
+// Concurrency: safe
 func GetTemplatesInstance() *Templates {
     return templates
 }
 
 // Create all templates from on-disk files.
-// NOTE: path is the root directory where templates reside.
+// REMARK: path is the root directory where templates reside.
+// Concurrency: unsafe
 func InitTemplates() error {
     templatesDir := paths.GetInstance().TemplatesDir
     templates = &Templates { m: make(map[string]*Template), dir: templatesDir, rwlock: new(sync.RWMutex) }
@@ -70,7 +72,8 @@ func InitTemplates() error {
     return nil
 }
 
-// Returns the list of templates for which TemplateKeeperFn returns true.
+// Returns a list of templates for which TemplateKeeperFn is true.
+// Concurrency: safe
 func (templates *Templates)GetNetworkSerializedTemplates(keepFn TemplateKeeperFn) []t.TemplateSerialized {
     templates.rwlock.RLock()
 
@@ -89,11 +92,17 @@ func (templates *Templates)GetNetworkSerializedTemplates(keepFn TemplateKeeperFn
     return list
 }
 
-// Returns a template by its ID.
+// Returns a template by its ID. Concurrency-safe version.
+// Concurrency: safe
 func (templates *Templates)GetTemplate(tplID uuid.UUID) (*Template, error) {
     templates.rwlock.RLock()
     defer templates.rwlock.RUnlock()
+    return templates.getTemplateUnsafe(tplID)
+}
 
+// Returns a template by its ID. Concurrency-unsafe version.
+// Concurrency: unsafe
+func (templates *Templates)getTemplateUnsafe(tplID uuid.UUID) (*Template, error) {
     template, exists := templates.m[tplID.String()]
     if !exists {
         return nil, errors.New("Template not found")
@@ -105,8 +114,10 @@ func (templates *Templates)GetTemplate(tplID uuid.UUID) (*Template, error) {
 // A template can only be deleted if no VM references it!
 // TODO: prevents deletion if the template's disk image is
 //       being exported by RouterTemplates.ExportDisk
+// Concurrency: safe
 func (templates *Templates)DeleteTemplate(tplID uuid.UUID, vms *VMs) error {
     id := tplID.String()
+
     templates.rwlock.Lock()
     defer templates.rwlock.Unlock()
     template, exists := templates.m[id]
@@ -126,7 +137,8 @@ func (templates *Templates)DeleteTemplate(tplID uuid.UUID, vms *VMs) error {
     return errors.New("Failed: template not found")
 }
 
-// Adds a template and writes its config file.
+// Adds a template, create its on-disk directory and config file.
+// Concurrency: safe
 func (templates *Templates)AddTemplate(template *Template) error {
     templates.rwlock.Lock()
     defer templates.rwlock.Unlock()
@@ -142,9 +154,13 @@ func (templates *Templates)AddTemplate(template *Template) error {
     return nil
 }
 
-// Edit a template's fields: name and/or access.
+// Edit a template's fields (name and/or access) and updates its config file accordingly.
+// Concurrency: safe
 func (templates *Templates)EditTemplate(tplID uuid.UUID, name, access string) error {
-    tpl, err := templates.GetTemplate(tplID)
+    templates.rwlock.RLock()
+    defer templates.rwlock.RUnlock()
+
+    tpl, err := templates.getTemplateUnsafe(tplID)
     if err != nil {
         return err
     }
diff --git a/src/server/vms/vm.go b/src/server/vms/vm.go
index b755a9d320e98289793ecfa9d5906193b29cb2ba..a45c423fd1b0dfa54d5d60af3897d573a782328d 100644
--- a/src/server/vms/vm.go
+++ b/src/server/vms/vm.go
@@ -6,17 +6,12 @@ import (
     "path"
     "errors"
     "strings"
-    "syscall"
     "net/mail"
-    "io/ioutil"
     "path/filepath"
     "encoding/json"
-    "encoding/base64"
     vmc "nexus-common/vm"
     "nexus-common/caps"
-    "nexus-server/qga"
     "nexus-server/exec"
-    "nexus-server/paths"
     "github.com/google/uuid"
     "github.com/go-playground/validator/v10"
     "github.com/sethvargo/go-password/password"
@@ -31,7 +26,7 @@ type (
         dir string         // VM directory
         qgaSock string     // QEMU Guest Agent (QGA) UNIX socket
         Run runStates
-        DiskBusy bool      // When true, the disk image must not be modified
+        DiskBusy bool      // When true, the disk image must not be modified (e.g. write access)
         mutex *sync.Mutex
     }
 
@@ -59,7 +54,8 @@ var passwordGen, _ = password.NewGenerator(&password.GeneratorInput{
     Digits: "123456789",
 })
 
-// Creates a VM.
+// Creates a VM and returns a pointer to it.
+// Concurrency: safe
 func NewVM(creatorEmail string, name string, cpus, ram int, nic vmc.NicType, usbDevs []string, templateID uuid.UUID, owner string) (*VM, error) {
     vmID, err := uuid.NewRandom()
 
@@ -88,6 +84,8 @@ func NewVM(creatorEmail string, name string, cpus, ram int, nic vmc.NicType, usb
     return vm, nil
 }
 
+// Returns a serialized version (to be written to disk) of the template.
+// Concurrency: safe
 func (vm *VM)SerializeToDisk() vmc.VMDiskSerialized {
     return vmc.VMDiskSerialized {
         ID: vm.v.ID,
@@ -102,6 +100,8 @@ func (vm *VM)SerializeToDisk() vmc.VMDiskSerialized {
     }
 }
 
+// Returns a serialized version (to be sent to the network) of the template.
+// Concurrency: safe
 func (vm *VM)SerializeToNetwork() vmc.VMNetworkSerialized {
     return vmc.VMNetworkSerialized {
         ID: vm.v.ID,
@@ -120,30 +120,45 @@ func (vm *VM)SerializeToNetwork() vmc.VMNetworkSerialized {
     }
 }
 
+// Returns a VM's ID.
+// Concurrency: safe
 func (vm *VM)GetID() uuid.UUID {
     return vm.v.ID
 }
 
+// Returns a VM's access.
+// Concurrency: safe
 func (vm *VM)GetAccess() map[string]caps.Capabilities {
     return vm.v.Access
 }
 
 // Returns true if the specified email is the VM's owner.
+// Concurrency: safe
 func (vm *VM)IsOwner(email string) bool {
     return email == vm.v.Owner
 }
 
+// Returns true if the VM is running.
+// Concurrency: safe
 func (vm *VM)IsRunning() bool {
     return vm.Run.State == vmc.StateRunning
 }
 
+// Returns true if the VM's disk is being modified (e.g. written).
+// Concurrency: safe
+func (vm *VM)IsDiskBusy() bool {
+    return vm.DiskBusy
+}
+
 // Returns the absolute path to the VM's disk image.
+// Concurrency: safe
 func (vm *VM)getDiskPath() string {
     path, _ := filepath.Abs(filepath.Join(vm.dir, vmDiskFile))
     return path
 }
 
-// Creates an empty VM.
+// Creates an empty VM and returns a pointer to it.
+// Concurrency: safe
 func newEmptyVM() *VM {
     return &VM {
         v: vmc.VMDiskSerialized {
@@ -165,7 +180,8 @@ func newEmptyVM() *VM {
     }
 }
 
-// Creates a VM from a vmConfFile and returns it.
+// Creates a VM from a configuration file (json) and returns a pointer to it.
+// Concurrency: safe
 func newVMFromFile(vmFile string) (*VM, error) {
     filein, err := os.OpenFile(vmFile, os.O_RDONLY, 0)
     if err != nil {
@@ -191,6 +207,8 @@ func newVMFromFile(vmFile string) (*VM, error) {
 }
 
 // Checks that the VM structure's fields are valid.
+// Returns an error if they are not.
+// Concurrency: safe
 func (vm *VM)validate() error {
     // Checks the capabilities are valid
     for email, accessCaps := range vm.v.Access {
@@ -224,10 +242,11 @@ func (vm *VM)validate() error {
     return nil
 }
 
-// Writes a VM's files:
-// 1) Creates the 3-directory structure.
-// 2) Writes vmConfFile.
-// 3) Creates vmDiskFile as an overlay on top of the template disk.
+// Creates the VM's directory and writes its files:
+// 1) Creates the 3-directory structure based on the VM's ID (UUID).
+// 2) Writes the VM configuration file (json).
+// 3) Creates the VM disk image as an overlay on top of the template disk image.
+// Concurrency: unsafe
 func (vm *VM)writeFiles() error {
     // Checks the template referenced by the VM exists.
     template, err := GetTemplatesInstance().GetTemplate(vm.v.TemplateID)
@@ -237,8 +256,9 @@ func (vm *VM)writeFiles() error {
 
     // Creates the 3-directory structure.
     if err = os.MkdirAll(vm.dir, 0750); err != nil {
-        log.Error("Failed creating VM dirs: "+err.Error())
-        return errors.New("Failed creating VM dirs: "+err.Error())
+        msg := "Failed creating VM dirs: "+err.Error()
+        log.Error(msg)
+        return errors.New(msg)
     }
 
     // Writes vmConfFile.
@@ -247,7 +267,7 @@ 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.
+    // REMARK: template and output file must be both specified as absolute paths.
     templateDiskFile := template.GetTemplateDiskPath()
 
     if err := exec.QemuImgCreate(templateDiskFile, vm.getDiskPath()); err != nil {
@@ -258,38 +278,33 @@ func (vm *VM)writeFiles() error {
     return nil
 }
 
-// Writes the VM config file (vmConfFile).
-// NOTE: does not check the template's validity!
+// Writes the VM config file (json).
+// REMARK: does not check whether the template is valid or not.
+// Concurrency: unsafe
 func (vm *VM)writeConfig() error {
     vmFile := filepath.Join(vm.dir, vmConfFile)
      file, err := os.Create(vmFile)
      if err != nil {
-         log.Error("Failed writing VM config file: "+err.Error())
-        return errors.New("Failed writing VM config file: "+err.Error())
+         msg := "Failed writing VM config file: "+err.Error()
+         log.Error(msg)
+        return errors.New(msg)
     }
     defer file.Close()
     encoder := json.NewEncoder(file)
     encoder.SetIndent("", "    ")
 
     if err = encoder.Encode(vm.SerializeToDisk()); err != nil {
-        log.Error("Failed encoding VM config file: "+err.Error())
-        return errors.New("Failed encoding VM config file: "+err.Error())
+        msg := "Failed encoding VM config file: "+err.Error()
+        log.Error(msg)
+        return errors.New(msg)
     }
 
     return nil
 }
 
-// Deletes the files associated to a VM.
-// The VM must be stopped before being deleted, otherwise an error is returned.
+// Deletes all files associated to a VM.
+// Concurrency: unsafe
 func (vm *VM)delete() error {
-    if vm.IsRunning() {
-        return errors.New("Failed deleting VM: VM must be stopped")
-    }
-
-    if vm.DiskBusy {
-        return errors.New("Failed deleting VM: disk is busy")
-    }
-
     // Deletes the VM's directory and its content.
     if err := os.RemoveAll(vm.dir); err != nil {
         log.Error("Failed deleting VM files: "+err.Error())
@@ -305,169 +320,9 @@ func (vm *VM)delete() error {
     return nil
 }
 
-// Starts a VM on the given port with the given password.
-func (vm *VM)start(port int, pwd string, endofExecFn endOfExecCallback) error {
-    if vm.DiskBusy {
-        return errors.New("Failed starting VM: disk is busy")
-    }
-
-    // Writes the password in a "secret" file.
-    pwdFile, err := vm.writeSecretFile(pwd)
-    if err != nil {
-        log.Error("Failed starting VM: error creating secret file: "+err.Error())
-        return errors.New("Failed starting VM: error creating secret file: "+err.Error())
-    }
-
-    if err = vm.runQEMU(port, pwd, pwdFile, endofExecFn); err != nil {
-        vm.removeSecretFile()
-        os.Remove(vm.qgaSock)  // If QEMU fails it's likely the Guest Agent file it created is still there.
-        return errors.New("Failed starting VM: error running QEMU: "+err.Error())
-    }
-
-    return nil
-}
-
-// Writes the specified password in Base64 in a "secret" file inside the VM directory.
-// Returns the file that was written if success.
-func (vm *VM)writeSecretFile(pwd string) (string, error) {
-    // Write the password in a "secret" file inside the VM directory.
-    pwdBase64 := base64.StdEncoding.EncodeToString([]byte(pwd))
-    content := []byte(pwdBase64)
-    pwdFile := filepath.Join(vm.dir, vmSecretFile)
-    if err := ioutil.WriteFile(pwdFile, content, 0600); err != nil {
-        return "", err
-    }
-    return pwdFile, nil
-}
-
-func (vm *VM)removeSecretFile() {
-    pwdFile := filepath.Join(vm.dir, vmSecretFile)
-    os.Remove(pwdFile)
-}
-
-// Kills a running VM.
-func (vm *VM)kill() error {
-    if !vm.IsRunning() {
-        return errors.New("Failed stopping VM: VM is not running")
-    }
-
-    // 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.SIGTERM); err != nil {
-        log.Error("Failed stopping VM: "+err.Error())
-        return errors.New("Failed stopping VM: "+err.Error())
-    }
-
-    return nil
-}
-
-// Gracefully shutdowns a running VM.
-// Uses QGA commands to talk to the guest OS, which means QEMU Guest Agent must be
-// running in the guest, otherwise nothing will happen. Furthermore, the guest OS must
-// already be running and ready to accept QGA commands.
-func (vm *VM)shutdown() error {
-    prefix := "Shutdown failed: "
-
-    if !vm.IsRunning() {
-        return errors.New(prefix+"VM is not running")
-    }
-
-    if vm.DiskBusy {
-        return errors.New(prefix+"VM disk is busy")
-    }
-
-    // Sends a QGA command to order the VM to shutdown.
-    con := qga.New()
-    if err := con.Open(vm.qgaSock); err != nil {
-        log.Error(prefix+"(open): "+err.Error())
-        return errors.New(prefix+"(open): "+err.Error())
-    }
-
-    if err := con.SendShutdown(); err != nil {
-        con.Close()
-        log.Error(prefix+"(send): "+err.Error())
-        return errors.New(prefix+"(send): "+err.Error())
-    }
-
-    if err := con.Close(); err != nil {
-        log.Error(prefix+"(close): "+err.Error())
-        return errors.New(prefix+"(close): "+err.Error())
-    }
-
-    return nil
-}
-
-// Reboots a running VM.
-// Uses QGA commands to talk to the VM, which means QEMU Guest Agent must be
-// running in the VM, otherwise it won't work.
-func (vm *VM)reboot() error {
-    prefix := "Shutdown failed: "
-
-    if !vm.IsRunning() {
-        return errors.New(prefix+"VM is not running")
-    }
-
-    if vm.DiskBusy {
-        return errors.New(prefix+"VM disk is busy")
-    }
-
-    // Sends a QGA command to order the VM to shutdown.
-    con := qga.New()
-    if err := con.Open(vm.qgaSock); err != nil {
-        log.Error(prefix+"(open): "+err.Error())
-        return errors.New(prefix+"(open): "+err.Error())
-    }
-
-    if err := con.SendReboot(); err != nil {
-        con.Close()
-        log.Error(prefix+"(send): "+err.Error())
-        return errors.New(prefix+"(send): "+err.Error())
-    }
-
-    if err := con.Close(); err != nil {
-        log.Error(prefix+"(close): "+err.Error())
-        return errors.New(prefix+"(close): "+err.Error())
-    }
-
-    return nil
-}
-
-// Executes the VM in QEMU using the specified spice port and password.
-func (vm *VM)runQEMU(port int, pwd, pwdFile string, endofExecFn endOfExecCallback) error {
-    pkiDir := paths.GetInstance().NexusPkiDir
-    cmd, err := exec.NewQemuSystem(vm.qgaSock, vm.v.Cpus, vm.v.Ram, string(vm.v.Nic), vm.v.UsbDevs, filepath.Join(vm.dir, vmDiskFile), port, pwdFile, pkiDir)
-    if err != nil {
-        return err
-    }
-
-    if err := cmd.Start(); err != nil {
-        log.Error("Failed executing VM "+vm.v.ID.String()+": exec.Start error: "+err.Error())
-        log.Error("Failed cmd: "+cmd.String())
-        return err
-    }
-
-    vm.Run = runStates { State: vmc.StateRunning, Pid: cmd.Process.Pid, Port: port, Pwd: pwd }
-
-    // Execute cmd.Wait() (which is a blocking call) inside a go-routine to avoid blocking.
-    // From here on, there are 2 flows of execution!
-    go func() {
-        if err := cmd.Wait(); err != nil {
-            log.Error("Failed executing VM "+vm.v.ID.String()+": exec.Wait error: "+err.Error())
-            log.Error("Failed cmd: "+cmd.String())
-        }
-        endofExecFn(vm)
-    } ()
-
-    return nil
-}
-
-// Resets a VM's states.
-func (vm *VM)resetStates() {
-    vm.Run = runStates { State: vmc.StateStopped, Pid: 0, Port: 0, Pwd: "" }
-}
-
 // Validates and convert a string of USB devices of the form "1fc9:001d,067b:2303"
 // into a slice of string where each element is a string of the form "1fc9:001d".
+// Concurrency: safe
 func validateUsbDevs(devs []string) error {
     for _, dev := range devs {
         ids := strings.Split(dev, ":")  // Extracts vendorID (vid) and productID (pid)
@@ -484,6 +339,7 @@ func validateUsbDevs(devs []string) error {
 
 // A USB ID is either a vendor ID or a product ID.
 // It's a string containing exactly 4 hexadecimal digits.
+// Concurrency: safe
 func isUsbId(s string) (bool) {
     if len(s) != 4 {
         return false
diff --git a/src/server/vms/vms.go b/src/server/vms/vms.go
index f925002a09f64ae502efe421e453e1105147ccad..b77fa11519a4e001f3f625c70f01026a0012a5b9 100644
--- a/src/server/vms/vms.go
+++ b/src/server/vms/vms.go
@@ -6,8 +6,12 @@ import (
     "sync"
     "math"
     "errors"
+    "syscall"
+    "io/ioutil"
     "path/filepath"
-    "nexus-common/vm"
+    "encoding/base64"
+    vmc "nexus-common/vm"
+    "nexus-server/qga"
     "nexus-common/caps"
     "nexus-common/params"
     "nexus-server/exec"
@@ -34,14 +38,16 @@ type (
 var log = logger.GetInstance()
 var vms *VMs
 
-// Returns a VMs "singleton".
+// Returns a VMs singleton to access this module's public functions.
 // IMPORTANT: the InitVMs function must have been previously called!
+// Concurrency: safe
 func GetVMsInstance() *VMs {
     return vms
 }
 
 // Creates all VMs from their files on disk.
-// NOTE: path is the root directory where VMs reside.
+// REMARK: path is the root directory where VMs reside.
+// Concurrency: unsafe
 func InitVMs() error {
     vmsDir := paths.GetInstance().VMsDir
     vms = &VMs { m: make(map[string]*VM), dir: vmsDir, rwlock: new(sync.RWMutex), usedRAM: 0 }
@@ -89,11 +95,12 @@ func InitVMs() error {
     return nil
 }
 
-// Returns the list of serialized VMs for which VMKeeperFn returns true.
-func (vms *VMs)GetNetworkSerializedVMs(keepFn VMKeeperFn) []vm.VMNetworkSerialized {
+// Returns the list of serialized VMs for which VMKeeperFn is true.
+// Concurrency: safe
+func (vms *VMs)GetNetworkSerializedVMs(keepFn VMKeeperFn) []vmc.VMNetworkSerialized {
     vms.rwlock.RLock()
 
-    list := []vm.VMNetworkSerialized{}
+    list := []vmc.VMNetworkSerialized{}
     for _, vm := range vms.m {
         vm.mutex.Lock()
         if keepFn(vm) {
@@ -110,13 +117,16 @@ func (vms *VMs)GetNetworkSerializedVMs(keepFn VMKeeperFn) []vm.VMNetworkSerializ
     return list
 }
 
-// Returns a VM by its ID.
+// Returns a VM by its ID. Concurrency-safe version.
+// Concurrency: safe
 func (vms *VMs)GetVM(vmID uuid.UUID) (*VM, error) {
     vms.rwlock.RLock()
     defer vms.rwlock.RUnlock()
     return vms.getVMUnsafe(vmID)
 }
 
+// Returns a VM by its ID. Concurrency-unsafe version.
+// Concurrency: unsafe
 func (vms *VMs)getVMUnsafe(vmID uuid.UUID) (*VM, error) {
     vm, exists := vms.m[vmID.String()]
     if !exists {
@@ -125,7 +135,8 @@ func (vms *VMs)getVMUnsafe(vmID uuid.UUID) (*VM, error) {
     return vm, nil
 }
 
-// Deletes a VM by its ID and deletes its files.
+// Deletes a VM by its ID and deletes its associated files.
+// Concurrency: safe
 func (vms *VMs)DeleteVM(vmID uuid.UUID) error {
     vms.rwlock.Lock()
     defer vms.rwlock.Unlock()
@@ -136,26 +147,28 @@ func (vms *VMs)DeleteVM(vmID uuid.UUID) error {
     }
 
     vm.mutex.Lock()
+    defer vm.mutex.Unlock()
 
     if vm.IsRunning() {
-        vm.mutex.Unlock()
-        return errors.New("VM must be stopped")
+        return errors.New("Failed deleting VM: VM must be stopped")
+    }
+
+    if vm.IsDiskBusy() {
+        return errors.New("Failed deleting VM: disk in use (busy)")
     }
 
     // Deletes the VM's files (and directories).
     if err := vm.delete(); err != nil {
-        vm.mutex.Unlock()
         return err
     }
 
-    vm.mutex.Unlock()
-
     // Removes the VM from the map.
     delete(vms.m, vmID.String())
     return nil
 }
 
-// Adds a VM and writes its files.
+// Adds a new VM and writes its associated files.
+// Concurrency: safe
 func (vms *VMs)AddVM(vm *VM) error {
     vm.mutex.Lock()
     defer vm.mutex.Unlock()
@@ -178,7 +191,10 @@ func (vms *VMs)AddVM(vm *VM) error {
 // Starts a VM by its ID, using the specified port and password.
 // If checkPort is true, a check is performed on the specified port and if it is already
 // in use, the function fails and returns a corresponding error.
+// Concurrency: safe
 func (vms *VMs)StartVMWithCreds(vmID uuid.UUID, port int, checkPort bool, pwd string) error {
+    prefix := "Failed starting VM: "
+
     vms.rwlock.Lock()
     defer vms.rwlock.Unlock()
 
@@ -191,28 +207,27 @@ func (vms *VMs)StartVMWithCreds(vmID uuid.UUID, port int, checkPort bool, pwd st
     defer vm.mutex.Unlock()
 
     if vm.IsRunning() {
-        return errors.New("Failed starting VM: VM already running")
+        return errors.New(prefix+"already running")
+    }
+
+    if vm.IsDiskBusy() {
+        return errors.New(prefix+"disk in use (busy)")
     }
 
     if checkPort {
         if vms.usedPorts[port] {
-            return errors.New("Failed starting VM: port already in use")
+            return errors.New(prefix+"port already in use")
         } else if !utils.IsPortAvailable(port) {
-            return errors.New("Failed starting VM: port not available")
-        } else {
-            vms.usedPorts[port] = true
+            return errors.New(prefix+"port not available")
         }
-    } else {
-        vms.usedPorts[port] = true
     }
 
     totalRAM, availRAM, err := utils.GetRAM()
     if err != nil {
-        vms.usedPorts[port] = false
-        return errors.New("Failed starting VM: failed obtaining memory info: "+err.Error())
+        return errors.New(prefix+"failed obtaining memory info: "+err.Error())
     }
 
-    // We estimate that KVM allows for ~30% RAM saving (due to page sharing across VMs).
+    // We estimate ~30% of RAM saving thanks to KSM (due to page sharing across VMs).
     estimatedVmRAM := int(math.Round(float64(vm.v.Ram)*(1.-c.KsmRamSaving)))
 
     vms.usedRAM += estimatedVmRAM
@@ -221,32 +236,96 @@ func (vms *VMs)StartVMWithCreds(vmID uuid.UUID, port int, checkPort bool, pwd st
     // otherwise, refuses to run it in order to avoid RAM saturation.
     if availRAM - vms.usedRAM <= int(math.Round(float64(totalRAM)*(1.-c.RamUsageLimit))) {
         vms.usedRAM -= estimatedVmRAM
-        vms.usedPorts[port] = false
-        return errors.New("Failed starting VM: insufficient free RAM")
+        return errors.New(prefix+"insufficient free RAM")
+    }
+
+    // Function that executes the VM in QEMU using the specified spice port and password.
+    runQemuFn := func(vm *VM, port int, pwd, pwdFile string, endofExecFn endOfExecCallback) error {
+        pkiDir := paths.GetInstance().NexusPkiDir
+        cmd, err := exec.NewQemuSystem(vm.qgaSock, vm.v.Cpus, vm.v.Ram, string(vm.v.Nic), vm.v.UsbDevs, filepath.Join(vm.dir, vmDiskFile), port, pwdFile, pkiDir)
+        if err != nil {
+            log.Error(prefix+"filepath join error: "+err.Error())
+            return err
+        }
+
+        if err := cmd.Start(); err != nil {
+            log.Error(prefix+vm.v.ID.String()+": exec.Start error: "+err.Error())
+            log.Error("Failed cmd: "+cmd.String())
+            return err
+        }
+
+        vm.Run = runStates { State: vmc.StateRunning, Pid: cmd.Process.Pid, Port: port, Pwd: pwd }
+        vm.DiskBusy = true
+
+        // Go routine that executes cmd.Wait() which is a blocking call (hence the go routine).
+        // From here on, there are 2 flows of execution!
+        go func() {
+            if err := cmd.Wait(); err != nil {
+                log.Error(prefix+vm.v.ID.String()+": exec.Wait error: "+err.Error())
+                log.Error("Failed cmd: "+cmd.String())
+            }
+            endofExecFn(vm)
+        } ()
+
+        return nil
+    }
+
+    // Function that deletes a VM's secret file.
+    removeSecretFileFn := func(vm *VM) {
+        pwdFile := filepath.Join(vm.dir, vmSecretFile)
+        os.Remove(pwdFile)
     }
 
-    // This callback is called once the VM started with vm.start terminates.
-    endofExecFn := func (vm *VM) {
+    // Function that starts a VM on the given port with the given password.
+    // endofExecFn is called once the VM's execution is over.
+    startFn := func(vm *VM, port int, pwd string, endofExecFn endOfExecCallback) error {
+        // Writes the password in Base64 in a secret file inside the VM's directory.
+        pwdBase64 := base64.StdEncoding.EncodeToString([]byte(pwd))
+        content := []byte(pwdBase64)
+        pwdFile := filepath.Join(vm.dir, vmSecretFile)
+        if err := ioutil.WriteFile(pwdFile, content, 0600); err != nil {
+            msg := prefix+"error creating secret file: "+err.Error()
+            log.Error(msg)
+            return errors.New(msg)
+        }
+
+        if err = runQemuFn(vm, port, pwd, pwdFile, endofExecFn); err != nil {
+            removeSecretFileFn(vm)
+            os.Remove(vm.qgaSock)  // If QEMU fails it's likely the Guest Agent file it created is still there.
+            return errors.New(prefix+"error running QEMU: "+err.Error())
+        }
+
+        return nil
+    }
+
+    // Function that resets the VM's states, frees its port and deletes its secret file.
+    // Called once the VM's execution is over.
+    endofExecFn := func(vm *VM) {
         vms.rwlock.Lock()
         vms.usedPorts[port] = false
         vms.usedRAM -= estimatedVmRAM
         vms.rwlock.Unlock()
         vm.mutex.Lock()
-        vm.removeSecretFile()
-        vm.resetStates()
+        removeSecretFileFn(vm)
+        // Resets VM states and disk busy flag.
+        vm.Run = runStates { State: vmc.StateStopped, Pid: 0, Port: 0, Pwd: "" }
+        vm.DiskBusy = false
         vm.mutex.Unlock()
     }
 
-    if err = vm.start(port, pwd, endofExecFn); err != nil {
+    vms.usedPorts[port] = true
+
+    if err = startFn(vm, port, pwd, endofExecFn); err != nil {
         vms.usedPorts[port] = false
         return err
     }
+
     return nil
 }
 
-// Allocates and returns a free port randomly chosen between VMSpiceMinPort and
-// VMSpiceMaxPort (inclusive).
-// Beware: this function updates the vms map.
+// Allocates and returns a free port randomly chosen within [VMSpiceMinPort,VMSpiceMaxPort].
+// REMARK: this function updates the vms map.
+// Concurrency: safe
 func (vms *VMs)allocateFreeRandomPort() int {
     vms.rwlock.Lock()
     defer vms.rwlock.Unlock()
@@ -264,6 +343,7 @@ func (vms *VMs)allocateFreeRandomPort() int {
 
 // Starts a VM by its ID using randomly generated port number and password.
 // Returns the port on which the VM is running and the access password.
+// Concurrency: safe
 func (vms *VMs)StartVM(vmID uuid.UUID) (int, string, error) {
     port := vms.allocateFreeRandomPort()
 
@@ -271,8 +351,9 @@ func (vms *VMs)StartVM(vmID uuid.UUID) (int, string, error) {
     // allowing upper and lower case letters, disallowing repeat characters.
     pwd, err := passwordGen.Generate(c.VMPwdLength, c.VMPwdDigitCount, c.VMPwdSymbolCount, false, c.VMPwdRepeatChars)
     if err != nil {
-        log.Error("Failed starting VM: password generation error: "+err.Error())
-        return -1, "", errors.New("Failed starting VM: password generation error: "+err.Error())
+        msg := "Failed starting VM: password generation error: "+err.Error()
+        log.Error(msg)
+        return -1, "", errors.New(msg)
     }
 
     if err = vms.StartVMWithCreds(vmID, port, false, pwd); err != nil {
@@ -282,6 +363,7 @@ func (vms *VMs)StartVM(vmID uuid.UUID) (int, string, error) {
 }
 
 // Kills a VM by its ID.
+// Concurrency: safe
 func (vms *VMs)KillVM(vmID uuid.UUID) error {
     vms.rwlock.RLock()
     defer vms.rwlock.RUnlock()
@@ -295,14 +377,22 @@ func (vms *VMs)KillVM(vmID uuid.UUID) error {
     defer vm.mutex.Unlock()
 
     if !vm.IsRunning() {
-        return errors.New("VM must be running")
+        return errors.New("Failed killing VM: VM must be running")
     }
 
-    err = vm.kill()
-    return err
+    // 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.SIGTERM); err != nil {
+        msg := "Failed killing VM: "+err.Error()
+        log.Error(msg)
+        return errors.New(msg)
+    }
+
+    return nil
 }
 
 // Gracefully stops a VM by its ID.
+// Concurrency: safe
 func (vms *VMs)ShutdownVM(vmID uuid.UUID) error {
     vms.rwlock.Lock()
     defer vms.rwlock.Unlock()
@@ -316,14 +406,42 @@ func (vms *VMs)ShutdownVM(vmID uuid.UUID) error {
     defer vm.mutex.Unlock()
 
     if !vm.IsRunning() {
-        return errors.New("VM must be running")
+        return errors.New("Shutdown failed: VM must be running")
     }
 
-    err = vm.shutdown()
-    return err
+    // Function that gracefully shutdowns a running VM.
+    // Uses QGA commands to talk to the guest OS, which means QEMU Guest Agent must be
+    // running in the guest, otherwise nothing will happen. Furthermore, the guest OS must
+    // already be running and ready to accept QGA commands.
+    shutdownFn := func(vm *VM) error {
+        prefix := "Shutdown failed: "
+
+        // Sends a QGA command to order the VM to shutdown.
+        con := qga.New()
+        if err := con.Open(vm.qgaSock); err != nil {
+            log.Error(prefix+"(open): "+err.Error())
+            return errors.New(prefix+"(open): "+err.Error())
+        }
+
+        if err := con.SendShutdown(); err != nil {
+            con.Close()
+            log.Error(prefix+"(send): "+err.Error())
+            return errors.New(prefix+"(send): "+err.Error())
+        }
+
+        if err := con.Close(); err != nil {
+            log.Error(prefix+"(close): "+err.Error())
+            return errors.New(prefix+"(close): "+err.Error())
+        }
+
+        return nil
+    }
+
+    return shutdownFn(vm)
 }
 
 // Reboots a VM by its ID.
+// Concurrency: safe
 func (vms *VMs)RebootVM(vmID uuid.UUID) error {
     vms.rwlock.Lock()
     defer vms.rwlock.Unlock()
@@ -337,14 +455,41 @@ func (vms *VMs)RebootVM(vmID uuid.UUID) error {
     defer vm.mutex.Unlock()
 
     if !vm.IsRunning() {
-        return errors.New("VM must be running")
+        return errors.New("Reboot failed: VM must be running")
+    }
+
+    // Function that reboots a running VM.
+    // Uses QGA commands to talk to the VM, which means QEMU Guest Agent must be
+    // running in the VM, otherwise it won't work.
+    rebootFn := func(vm *VM) error {
+        prefix := "Reboot failed: "
+
+        // Sends a QGA command to order the VM to shutdown.
+        con := qga.New()
+        if err := con.Open(vm.qgaSock); err != nil {
+            log.Error(prefix+"(open): "+err.Error())
+            return errors.New(prefix+"(open): "+err.Error())
+        }
+
+        if err := con.SendReboot(); err != nil {
+            con.Close()
+            log.Error(prefix+"(send): "+err.Error())
+            return errors.New(prefix+"(send): "+err.Error())
+        }
+
+        if err := con.Close(); err != nil {
+            log.Error(prefix+"(close): "+err.Error())
+            return errors.New(prefix+"(close): "+err.Error())
+        }
+
+        return nil
     }
 
-    err = vm.reboot()
-    return err
+    return rebootFn(vm)
 }
 
 // Returns true if the given template is used by a VM.
+// Concurrency: safe
 func (vms *VMs)IsTemplateUsed(templateID string) bool {
     vms.rwlock.RLock()
     defer vms.rwlock.RUnlock()
@@ -361,6 +506,7 @@ func (vms *VMs)IsTemplateUsed(templateID string) bool {
 }
 
 // Edit a VM' specs: name, cpus, ram, nic
+// Concurrency: safe
 func (vms *VMs)EditVM(vmID uuid.UUID, p *params.VMEdit) error {
     vms.rwlock.Lock()
     defer vms.rwlock.Unlock()
@@ -373,10 +519,6 @@ func (vms *VMs)EditVM(vmID uuid.UUID, p *params.VMEdit) error {
     vm.mutex.Lock()
     defer vm.mutex.Unlock()
 
-    if vm.IsRunning() {
-        return errors.New("VM must be stopped")
-    }
-
     // Only updates fields that have changed.
     oriVal := vm.v  // Saves original VM values.
 
@@ -412,6 +554,7 @@ func (vms *VMs)EditVM(vmID uuid.UUID, p *params.VMEdit) error {
 // Set a VM's Access for a given user (email).
 // user is the currently logged user
 // destUserEmail is the email of the user for which to modify the access
+// Concurrency: safe
 func (vms *VMs)SetVMAccess(vmID uuid.UUID, user *users.User, destUserEmail string, newAccess caps.Capabilities) error {
     if err := caps.ValidateVMAccessCaps(newAccess); err != nil {
         return err
@@ -429,10 +572,6 @@ func (vms *VMs)SetVMAccess(vmID uuid.UUID, user *users.User, destUserEmail strin
     vm.mutex.Lock()
     defer vm.mutex.Unlock()
 
-    if vm.IsRunning() {
-        return errors.New("VM must be stopped")
-    }
-
     // If user has VM_SET_ACCESS_ANY, modify is allowed.
     if !user.HasCapability(caps.CAP_VM_SET_ACCESS_ANY) {
         // If user is the VM's owner, modify is allowed.
@@ -458,6 +597,7 @@ func (vms *VMs)SetVMAccess(vmID uuid.UUID, user *users.User, destUserEmail strin
 // Remove a VM's Access for a given user (email).
 // user is the currently logged user
 // destUserEmail is the email of the user for which to modify the access
+// Concurrency: safe
 func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, user *users.User, destUserEmail string) error {
     vms.rwlock.Lock()
     defer vms.rwlock.Unlock()
@@ -471,10 +611,6 @@ func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, user *users.User, destUserEmail st
     vm.mutex.Lock()
     defer vm.mutex.Unlock()
 
-    if vm.IsRunning() {
-        return errors.New("VM must be stopped")
-    }
-
     // If user has VM_SET_ACCESS_ANY, modify is allowed.
     if !user.HasCapability(caps.CAP_VM_SET_ACCESS_ANY) {
         // If user is the VM's owner, modify is allowed.
@@ -503,31 +639,63 @@ func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, user *users.User, destUserEmail st
 }
 
 // Exports a VM's directory and its subdirectories into a tar.gz archive on the host.
+// Technically, extracting files from a running VM should work, but some files might be inconsistent.
+// In consequence, we forbid this action on a running VM.
+// Concurrency: safe
 func (vms *VMs)ExportVMFiles(vm *VM, vmDir, tarGzFile string) error {
+    prefix := "Failed exporting files from VM: "
+
     vm.mutex.Lock()
     defer vm.mutex.Unlock()
+
+    if vm.IsRunning() {
+        return errors.New(prefix+"VM must be stopped")
+    }
+
+    if vm.IsDiskBusy() {
+        return errors.New(prefix+"disk in use (busy)")
+    }
+
+    vm.DiskBusy = true
+    defer func(vm *VM) { vm.DiskBusy = false }(vm)
+
     vmDisk := vm.getDiskPath()
-    return exec.CopyFromVM(vmDisk, vmDir, tarGzFile)
+
+    if err := exec.CopyFromVM(vmDisk, vmDir, tarGzFile); err != nil {
+        msg := prefix+err.Error()
+        log.Error(msg)
+        return errors.New(msg)
+    }
+
+    return nil
 }
 
-// Imports files from a tar.gz archive into a VM's filesystem, in a specified directory.
+// Imports files from a tar.gz archive into a VM disk image (guest's filesystem), in a specified directory.
+// Concurrency: safe
 func (vms *VMs)ImportFilesToVM(vm *VM, tarGzFile, vmDir string) error {
+    prefix := "Failed importing files into VM: "
+
     vm.mutex.Lock()
     defer vm.mutex.Unlock()
 
     if vm.IsRunning() {
-        return errors.New("VM must be stopped")
+        return errors.New(prefix+"VM must be stopped")
     }
 
-    // Marks VM as "busy", meaning its disk file is being accessed for a possibly long time.
-    if vm.DiskBusy {
-        return errors.New("Cannot import files into VM: disk is already busy")
+    if vm.IsDiskBusy() {
+        return errors.New(prefix+"disk in use (busy)")
     }
-    vm.DiskBusy = true
 
-    // Clears the VM from being busy.
+    vm.DiskBusy = true
     defer func(vm *VM) { vm.DiskBusy = false }(vm)
 
     vmDisk := vm.getDiskPath()
-    return exec.CopyToVM(vmDisk, tarGzFile, vmDir)
+
+    if err := exec.CopyToVM(vmDisk, tarGzFile, vmDir); err != nil {
+        msg := prefix+err.Error()
+        log.Error(msg)
+        return errors.New(msg)
+    }
+
+    return nil
 }