diff --git a/cmd/containers-storage/copy.go b/cmd/containers-storage/copy.go index 13292de9c5..ead9af57ab 100644 --- a/cmd/containers-storage/copy.go +++ b/cmd/containers-storage/copy.go @@ -59,7 +59,7 @@ func copyContent(flags *mflag.FlagSet, action string, m storage.Store, args []st return 1 } target = filepath.Join(targetMount, targetParts[1]) - defer m.Unmount(targetLayer.ID) + defer m.Unmount(targetLayer.ID, false) } args = args[:len(args)-1] for _, srcSpec := range args { @@ -83,7 +83,7 @@ func copyContent(flags *mflag.FlagSet, action string, m storage.Store, args []st return 1 } source = filepath.Join(sourceMount, sourceParts[1]) - defer m.Unmount(sourceLayer.ID) + defer m.Unmount(sourceLayer.ID, false) } archiver := chrootarchive.NewArchiverWithChown(tarIDMappings, chownOpts, untarIDMappings) if err := archiver.CopyWithTar(source, target); err != nil { diff --git a/cmd/containers-storage/main.go b/cmd/containers-storage/main.go index 1d5a8b17b0..f7c2fc39f7 100644 --- a/cmd/containers-storage/main.go +++ b/cmd/containers-storage/main.go @@ -24,6 +24,7 @@ type command struct { var ( commands = []command{} jsonOutput = false + force = false ) func main() { diff --git a/cmd/containers-storage/mount.go b/cmd/containers-storage/mount.go index 70075b2f7a..7416ea70de 100644 --- a/cmd/containers-storage/mount.go +++ b/cmd/containers-storage/mount.go @@ -51,11 +51,15 @@ func unmount(flags *mflag.FlagSet, action string, m storage.Store, args []string mes := []mountPointError{} errors := false for _, arg := range args { - err := m.Unmount(arg) + mounted, err := m.Unmount(arg, force) errText := "" if err != nil { errText = fmt.Sprintf("%v", err) errors = true + } else { + if !mounted { + fmt.Printf("%s mountpoint unmounted\n", arg) + } } mes = append(mes, mountPointError{arg, errText}) } @@ -74,6 +78,37 @@ func unmount(flags *mflag.FlagSet, action string, m storage.Store, args []string return 0 } +func mounted(flags *mflag.FlagSet, action string, m storage.Store, args []string) int { + mes := []mountPointError{} + errors := false + for _, arg := range args { + mounted, err := m.Mounted(arg) + errText := "" + if err != nil { + errText = fmt.Sprintf("%v", err) + errors = true + } else { + if mounted { + fmt.Printf("%s mounted\n", arg) + } + } + mes = append(mes, mountPointError{arg, errText}) + } + if jsonOutput { + json.NewEncoder(os.Stdout).Encode(mes) + } else { + for _, me := range mes { + if me.Error != "" { + fmt.Fprintf(os.Stderr, "%s checking if %s is mounted\n", me.Error, me.ID) + } + } + } + if errors { + return 1 + } + return 0 +} + func init() { commands = append(commands, command{ names: []string{"mount"}, @@ -92,6 +127,17 @@ func init() { usage: "Unmount a layer or container", minArgs: 1, action: unmount, + addFlags: func(flags *mflag.FlagSet, cmd *command) { + flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output") + flags.BoolVar(&force, []string{"-force", "f"}, jsonOutput, "Force the umount") + }, + }) + commands = append(commands, command{ + names: []string{"mounted"}, + optionsHelp: "LayerOrContainerNameOrID", + usage: "Check if a file system is mounted", + minArgs: 1, + action: mounted, addFlags: func(flags *mflag.FlagSet, cmd *command) { flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output") }, diff --git a/layers.go b/layers.go index f9006c7e63..4baa5f1862 100644 --- a/layers.go +++ b/layers.go @@ -211,7 +211,10 @@ type LayerStore interface { Mount(id, mountLabel string) (string, error) // Unmount unmounts a layer when it is no longer in use. - Unmount(id string) error + Unmount(id string, force bool) (bool, error) + + // Mounted returns whether or not the layer is mounted. + Mounted(id string) (bool, error) // ParentOwners returns the UIDs and GIDs of parents of the layer's mountpoint // for which the layer's UID and GID maps don't contain corresponding entries. @@ -624,6 +627,14 @@ func (r *layerStore) Create(id string, parent *Layer, names []string, mountLabel return r.CreateWithFlags(id, parent, names, mountLabel, options, moreOptions, writeable, nil) } +func (r *layerStore) Mounted(id string) (bool, error) { + layer, ok := r.lookup(id) + if !ok { + return false, ErrLayerUnknown + } + return layer.MountCount > 0, nil +} + func (r *layerStore) Mount(id, mountLabel string) (string, error) { if !r.IsReadWrite() { return "", errors.Wrapf(ErrStoreIsReadOnly, "not allowed to update mount locations for layers at %q", r.mountspath()) @@ -652,21 +663,24 @@ func (r *layerStore) Mount(id, mountLabel string) (string, error) { return mountpoint, err } -func (r *layerStore) Unmount(id string) error { +func (r *layerStore) Unmount(id string, force bool) (bool, error) { if !r.IsReadWrite() { - return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to update mount locations for layers at %q", r.mountspath()) + return false, errors.Wrapf(ErrStoreIsReadOnly, "not allowed to update mount locations for layers at %q", r.mountspath()) } layer, ok := r.lookup(id) if !ok { layerByMount, ok := r.bymount[filepath.Clean(id)] if !ok { - return ErrLayerUnknown + return false, ErrLayerUnknown } layer = layerByMount } + if force { + layer.MountCount = 1 + } if layer.MountCount > 1 { layer.MountCount-- - return r.Save() + return true, r.Save() } err := r.driver.Put(id) if err == nil || os.IsNotExist(err) { @@ -675,9 +689,9 @@ func (r *layerStore) Unmount(id string) error { } layer.MountCount-- layer.MountPoint = "" - err = r.Save() + return false, r.Save() } - return err + return true, err } func (r *layerStore) ParentOwners(id string) (uids, gids []int, err error) { @@ -797,10 +811,8 @@ func (r *layerStore) Delete(id string) error { return ErrLayerUnknown } id = layer.ID - for layer.MountCount > 0 { - if err := r.Unmount(id); err != nil { - return err - } + if _, err := r.Unmount(id, true); err != nil { + return err } err := r.driver.Remove(id) if err == nil { @@ -917,7 +929,8 @@ func (s *simpleGetCloser) Get(path string) (io.ReadCloser, error) { } func (s *simpleGetCloser) Close() error { - return s.r.Unmount(s.id) + _, err := s.r.Unmount(s.id, false) + return err } func (r *layerStore) newFileGetter(id string) (drivers.FileGetCloser, error) { diff --git a/layers_ffjson.go b/layers_ffjson.go index 125b5d8c97..09b5d0f33e 100644 --- a/layers_ffjson.go +++ b/layers_ffjson.go @@ -1,5 +1,5 @@ // Code generated by ffjson . DO NOT EDIT. -// source: layers.go +// source: ./layers.go package storage diff --git a/store.go b/store.go index bafe011bf4..41b66a356f 100644 --- a/store.go +++ b/store.go @@ -262,8 +262,11 @@ type Store interface { Mount(id, mountLabel string) (string, error) // Unmount attempts to unmount a layer, image, or container, given an ID, a - // name, or a mount path. - Unmount(id string) error + // name, or a mount path. Returns whether or not the layer is still mounted. + Unmount(id string, force bool) (bool, error) + + // Unmount attempts to discover whether the specified id is mounted. + Mounted(id string) (bool, error) // Changes returns a summary of the changes which would need to be made // to one layer to make its contents the same as a second layer. If @@ -2245,13 +2248,30 @@ func (s *store) Mount(id, mountLabel string) (string, error) { return "", ErrLayerUnknown } -func (s *store) Unmount(id string) error { +func (s *store) Mounted(id string) (bool, error) { if layerID, err := s.ContainerLayerID(id); err == nil { id = layerID } rlstore, err := s.LayerStore() if err != nil { - return err + return false, err + } + rlstore.Lock() + defer rlstore.Unlock() + if modified, err := rlstore.Modified(); modified || err != nil { + rlstore.Load() + } + + return rlstore.Mounted(id) +} + +func (s *store) Unmount(id string, force bool) (bool, error) { + if layerID, err := s.ContainerLayerID(id); err == nil { + id = layerID + } + rlstore, err := s.LayerStore() + if err != nil { + return false, err } rlstore.Lock() defer rlstore.Unlock() @@ -2259,9 +2279,9 @@ func (s *store) Unmount(id string) error { rlstore.Load() } if rlstore.Exists(id) { - return rlstore.Unmount(id) + return rlstore.Unmount(id, force) } - return ErrLayerUnknown + return false, ErrLayerUnknown } func (s *store) Changes(from, to string) ([]archive.Change, error) { @@ -2811,7 +2831,7 @@ func (s *store) Shutdown(force bool) ([]string, error) { mounted = append(mounted, layer.ID) if force { for layer.MountCount > 0 { - err2 := rlstore.Unmount(layer.ID) + _, err2 := rlstore.Unmount(layer.ID, force) if err2 != nil { if err == nil { err = err2 diff --git a/tests/mount-layer.bats b/tests/mount-layer.bats new file mode 100644 index 0000000000..9fbfce62b5 --- /dev/null +++ b/tests/mount-layer.bats @@ -0,0 +1,81 @@ +#!/usr/bin/env bats + +load helpers + +@test "mount-layer" { + # Create a layer. + run storage --debug=false create-layer + [ "$status" -eq 0 ] + [ "$output" != "" ] + layer="$output" + + # Mount the layer. + run storage --debug=false mount $layer + [ "$status" -eq 0 ] + [ "$output" != "" ] + # Check if layer is mounted. + run storage --debug=false mounted $layer + [ "$status" -eq 0 ] + [ "$output" == "$layer mounted" ] + # Unmount the layer. + run storage --debug=false unmount $layer + [ "$status" -eq 0 ] + [ "$output" != "" ] + # Make sure layer is not mounted. + run storage --debug=false mounted $layer + [ "$status" -eq 0 ] + [ "$output" == "" ] + + # Mount the layer twice. + run storage --debug=false mount $layer + [ "$status" -eq 0 ] + [ "$output" != "" ] + run storage --debug=false mount $layer + [ "$status" -eq 0 ] + [ "$output" != "" ] + # Check if layer is mounted. + run storage --debug=false mounted $layer + [ "$status" -eq 0 ] + [ "$output" == "$layer mounted" ] + # Unmount the second layer. + run storage --debug=false unmount $layer + [ "$status" -eq 0 ] + [ "$output" == "" ] + # Check if layer is mounted. + run storage --debug=false mounted $layer + [ "$status" -eq 0 ] + [ "$output" == "$layer mounted" ] + # Unmount the first layer. + run storage --debug=false unmount $layer + [ "$status" -eq 0 ] + [ "$output" != "" ] + # Make sure layer is not mounted. + run storage --debug=false mounted $layer + [ "$status" -eq 0 ] + [ "$output" == "" ] + + + # Mount the layer twice and force umount. + run storage --debug=false mount $layer + [ "$status" -eq 0 ] + [ "$output" != "" ] + run storage --debug=false mount $layer + [ "$status" -eq 0 ] + [ "$output" != "" ] + # Check if layer is mounted. + run storage --debug=false mounted $layer + [ "$status" -eq 0 ] + [ "$output" == "$layer mounted" ] + # Unmount all layers. + run storage --debug=false unmount --force $layer + [ "$status" -eq 0 ] + [ "$output" != "" ] + # Make sure no layers are mounted. + run storage --debug=false mounted $layer + [ "$status" -eq 0 ] + [ "$output" == "" ] + + # Delete the first layer + run storage delete-layer $layer + [ "$status" -eq 0 ] +}