Skip to content

Commit

Permalink
Fix docker cp
Browse files Browse the repository at this point in the history
  • Loading branch information
AnderG committed Jul 19, 2024
1 parent 1bfffce commit bb0d010
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 72 deletions.
110 changes: 110 additions & 0 deletions activemount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"io"
"os"

"github.com/docker/go-plugins-helpers/volume"
)

type ActiveMount struct {
UsageCount int
}

func (d *DockerOnTop) Activate(request *volume.MountRequest, activemountsdir lockedFile) (bool, error) {
var activeMount ActiveMount
var result bool

_, readDirErr := activemountsdir.ReadDir(1) // Check if there are any files inside activemounts dir
if readDirErr == nil {
// There is something no need to mount the filesystem again
result = false
} else if errors.Is(readDirErr, io.EOF) {
// The directory is empty, mount the filesystem
result = true
} else {
log.Errorf("Failed to list the activemounts directory: %v", readDirErr)
return false, fmt.Errorf("failed to list activemounts/ %v", readDirErr)
}

activemountFilePath := d.activemountsdir(request.Name) + request.ID
file, err := os.Open(activemountFilePath)

if err == nil {
// The file can exist from a previous mount when doing a docker cp on an already mounted container, no need to mount the filesystem again
payload, _ := io.ReadAll(file)
json.Unmarshal(payload, &activeMount)
file.Close()
} else if os.IsNotExist(err) {
// Default case, we need to create a new active mount, the filesystem needs to be mounted
activeMount = ActiveMount{UsageCount: 0}
} else {
log.Errorf("Active mount file %s exists but cannot be read.", activemountFilePath)
return false, fmt.Errorf("active mount file %s exists but cannot be read", activemountFilePath)
}

activeMount.UsageCount++

payload, _ := json.Marshal(activeMount)
err = os.WriteFile(activemountFilePath, payload, 0o666)
if err != nil {
log.Errorf("Active mount file %s cannot be written.", activemountFilePath)
return false, fmt.Errorf("active mount file %s cannot be written", activemountFilePath)
}

return result, nil
}

func (d *DockerOnTop) Deactivate(request *volume.UnmountRequest, activemountsdir lockedFile) (bool, error) {

dirEntries, readDirErr := activemountsdir.ReadDir(2) // Check if there is any _other_ container using the volume
if errors.Is(readDirErr, io.EOF) {
// If directory is empty, unmount overlay and clean up
log.Errorf("There are no active mount files and one was expected. Unmounting.")
return true, fmt.Errorf("there are no active mount files and one was expected. Unmounting")
} else if readDirErr != nil {
log.Errorf("Failed to list the activemounts directory: %v", readDirErr)
return false, fmt.Errorf("failed to list activemounts/ %v", readDirErr)
}

var activeMount ActiveMount
activemountFilePath := d.activemountsdir(request.Name) + request.ID

otherVolumesPresent := len(dirEntries) > 1 || dirEntries[0].Name() != request.ID

file, err := os.Open(activemountFilePath)

if err == nil {
payload, _ := io.ReadAll(file)
json.Unmarshal(payload, &activeMount)
file.Close()
} else if os.IsNotExist(err) {
log.Errorf("The active mount file %s was expected but is not there", activemountFilePath)
return !otherVolumesPresent, fmt.Errorf("the active mount file %s was expected but is not there", activemountFilePath)
} else {
log.Errorf("The active mount file %s could not be opened", activemountFilePath)
return false, fmt.Errorf("the active mount file %s could not be opened", activemountFilePath)
}

activeMount.UsageCount--

if activeMount.UsageCount == 0 {
err := os.Remove(activemountFilePath)
if err != nil {
log.Errorf("The active mount file %s could not be deleted", activemountFilePath)
return false, fmt.Errorf("the active mount file %s could not be deleted", activemountFilePath)
}
return !otherVolumesPresent, nil
} else {
payload, _ := json.Marshal(activeMount)
err = os.WriteFile(activemountFilePath, payload, 0o666)
if err != nil {
log.Errorf("The active mount file %s could not be updated", activemountFilePath)
return false, fmt.Errorf("the active mount file %s could not be updated", activemountFilePath)
}
return false, nil
}
}
84 changes: 12 additions & 72 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"errors"
"fmt"
"io"
"os"
"regexp"
"strings"
Expand Down Expand Up @@ -184,10 +183,10 @@ func (d *DockerOnTop) Mount(request *volume.MountRequest) (*volume.MountResponse
}
defer activemountsdir.Close() // There is nothing I could do about the error (logging is performed inside `Close()` anyway)

_, readDirErr := activemountsdir.ReadDir(1) // Check if there are any files inside activemounts dir
if errors.Is(readDirErr, io.EOF) {
// No files => no other containers are using the volume. Need to mount the overlay

mount, err := d.Activate(request, activemountsdir)
if err != nil {
return nil, internalError("failed to activate the active mount:", err)
} else if mount {
lowerdir := thisVol.BaseDirPath
upperdir := d.upperdir(request.Name)
workdir := d.workdir(request.Name)
Expand All @@ -210,48 +209,9 @@ func (d *DockerOnTop) Mount(request *volume.MountRequest) (*volume.MountResponse
log.Errorf("Failed to mount overlay for volume %s: %v", request.Name, err)
return nil, internalError("failed to mount overlay", err)
}

log.Debugf("Mounted volume %s at %s", request.Name, mountpoint)
} else if err == nil {
log.Debugf("Volume %s is already mounted for some other container. Indicating success without remounting",
request.Name)
} else {
log.Errorf("Failed to list the activemounts directory: %v", err)
return nil, internalError("failed to list activemounts/", err)
}

activemountFilePath := d.activemountsdir(request.Name) + request.ID
f, err := os.Create(activemountFilePath)
if err == nil {
// We don't care about the file's contents
_ = f.Close()
} else {
if os.IsExist(err) {
// Super weird. I can't imagine why this would happen.
log.Warningf("Active mount %s already exists (but it shouldn't...)", activemountFilePath)
} else {
// A really bad situation!
// We successfully mounted (`syscall.Mount`) the volume but failed to put information about the container
// using the volume. In the worst case (if we just created the volume) the following happens:
// Using the plugin, it is now impossible to unmount the volume (this container is not created, so there's
// no one to trigger `.Unmount()`) and impossible to remove (the directory mountpoint/ is a mountpoint, so
// attempting to remove it will fail with `syscall.EBUSY`).
// It is possible to mount the volume again: a new overlay will be mounted, shadowing the previous one.
// The new overlay will be possible to unmount but, as the old overlay remains, the Unmount method won't
// succeed because the attempt to remove mountpoint/ will result in `syscall.EBUSY`.
//
// Thus, a human interaction is required.
//
// (if it's not us who actually mounted the overlay, then the situation isn't too bad: no new container is
// started, the error is reported to the end user).
log.Criticalf("Failed to create active mount file: %v. If no other container was currently "+
"using the volume, this volume's state is now invalid. A human interaction or a reboot is required",
err)
return nil, fmt.Errorf("docker-on-top internal error: failed to create an active mount file: %w. "+
"The volume is now locked. Make sure that no other container is using the volume, then run "+
"`unmount %s` to unlock it. Human interaction is required. Please, report this bug",
err, mountpoint)
}
log.Debugf("Volume %s already mounted at %s", request.Name, mountpoint)
}

return &response, nil
Expand All @@ -273,40 +233,20 @@ func (d *DockerOnTop) Unmount(request *volume.UnmountRequest) error {
}
defer activemountsdir.Close() // There's nothing I could do about the error if it occurs

dirEntries, readDirErr := activemountsdir.ReadDir(2) // Check if there is any _other_ container using the volume
if len(dirEntries) == 1 || errors.Is(readDirErr, io.EOF) {
// If just one entry or directory is empty, unmount overlay and clean up

unmount, err := d.Deactivate(request, activemountsdir)
if err != nil {
return internalError("failed to deactivate the active mount:", err)
} else if unmount {
err = syscall.Unmount(d.mountpointdir(request.Name), 0)
if err != nil {
log.Errorf("Failed to unmount %s: %v", d.mountpointdir(request.Name), err)
return err
}

err = d.volumeTreePostUnmount(request.Name)
// Don't return yet. The above error will be returned later
} else if readDirErr == nil {
log.Debugf("Volume %s is still mounted in some other container. Indicating success without unmounting",
request.Name)
} else {
log.Errorf("Failed to list the activemounts directory: %v", err)
return internalError("failed to list activemounts/", err)
}

activemountFilePath := d.activemountsdir(request.Name) + request.ID
err2 := os.Remove(activemountFilePath)
if os.IsNotExist(err2) {
log.Warningf("Failed to remove %s because it does not exist (but it should...)", activemountFilePath)
} else if err2 != nil {
// Another pretty bad situation. Even though we are no longer using the volume, it is seemingly in use by us
// because we failed to remove the file corresponding to this container.
log.Criticalf("Failed to remove the active mount file: %v. The volume is now considered used by a container "+
"that no longer exists", err)
// The user most likely won't see this error message due to daemon not showing unmount errors to the
// `docker run` clients :((
return fmt.Errorf("docker-on-top internal error: failed to remove the active mount file: %w. The volume is "+
"now considered used by a container that no longer exists. Human interaction is required: remove the file "+
"manually to fix the problem", err)
log.Debugf("Unmounted volume %s", request.Name)
} else {
log.Debugf("Volume %s is still mounted. Indicating success without unmounting", request.Name)
}

// Report an error during cleanup, if any
Expand Down

0 comments on commit bb0d010

Please sign in to comment.