solve incorrect unmount

1. add WithTempMount for better unmount and remove
2. solve incorrect unmount for
   diff.DiffMounts,
   diff.Apply,
   oci.WithUsername,
   oci.WithUserID,
   remapRootFS

Signed-off-by: yanxuean <yan.xuean@zte.com.cn>
This commit is contained in:
yanxuean 2018-01-05 22:43:24 +08:00
parent 1a560540b9
commit cb58bb885a
4 changed files with 200 additions and 229 deletions

View File

@ -6,7 +6,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"syscall" "syscall"
@ -188,21 +187,9 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
} }
func remapRootFS(mounts []mount.Mount, uid, gid uint32) error { func remapRootFS(mounts []mount.Mount, uid, gid uint32) error {
root, err := ioutil.TempDir("", "ctd-remap") return mount.WithTempMount(mounts, func(root string) error {
if err != nil { return filepath.Walk(root, incrementFS(root, uid, gid))
return err })
}
defer os.Remove(root)
for _, m := range mounts {
if err := m.Mount(root); err != nil {
return err
}
}
err = filepath.Walk(root, incrementFS(root, uid, gid))
if uerr := mount.Unmount(root, 0); err == nil {
err = uerr
}
return err
} }
func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc { func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os"
"strings" "strings"
"time" "time"
@ -90,60 +89,49 @@ func (s *walkingDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts
} }
} }
dir, err := ioutil.TempDir("", "extract-") var ocidesc ocispec.Descriptor
if err != nil { if err := mount.WithTempMount(mounts, func(root string) error {
return emptyDesc, errors.Wrap(err, "failed to create temporary directory") ra, err := s.store.ReaderAt(ctx, desc.Digest)
}
// We change RemoveAll to Remove so that we either leak a temp dir
// if it fails but not RM snapshot data. refer to #1868 #1785
defer os.Remove(dir)
if err := mount.All(mounts, dir); err != nil {
return emptyDesc, errors.Wrap(err, "failed to mount")
}
defer func() {
if uerr := mount.Unmount(dir, 0); uerr != nil {
if err == nil {
err = uerr
}
}
}()
ra, err := s.store.ReaderAt(ctx, desc.Digest)
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to get reader from content store")
}
defer ra.Close()
r := content.NewReader(ra)
if isCompressed {
ds, err := compression.DecompressStream(r)
if err != nil { if err != nil {
return emptyDesc, err return errors.Wrap(err, "failed to get reader from content store")
} }
defer ds.Close() defer ra.Close()
r = ds
}
digester := digest.Canonical.Digester() r := content.NewReader(ra)
rc := &readCounter{ if isCompressed {
r: io.TeeReader(r, digester.Hash()), ds, err := compression.DecompressStream(r)
} if err != nil {
return err
}
defer ds.Close()
r = ds
}
if _, err := archive.Apply(ctx, dir, rc); err != nil { digester := digest.Canonical.Digester()
rc := &readCounter{
r: io.TeeReader(r, digester.Hash()),
}
if _, err := archive.Apply(ctx, root, rc); err != nil {
return err
}
// Read any trailing data
if _, err := io.Copy(ioutil.Discard, rc); err != nil {
return err
}
ocidesc = ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageLayer,
Size: rc.c,
Digest: digester.Digest(),
}
return nil
}); err != nil {
return emptyDesc, err return emptyDesc, err
} }
return ocidesc, nil
// Read any trailing data
if _, err := io.Copy(ioutil.Discard, rc); err != nil {
return emptyDesc, err
}
return ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageLayer,
Size: rc.c,
Digest: digester.Digest(),
}, nil
} }
// DiffMounts creates a diff between the given mounts and uploads the result // DiffMounts creates a diff between the given mounts and uploads the result
@ -168,108 +156,85 @@ func (s *walkingDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount
default: default:
return emptyDesc, errors.Wrapf(errdefs.ErrNotImplemented, "unsupported diff media type: %v", config.MediaType) return emptyDesc, errors.Wrapf(errdefs.ErrNotImplemented, "unsupported diff media type: %v", config.MediaType)
} }
aDir, err := ioutil.TempDir("", "left-")
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to create temporary directory")
}
defer os.Remove(aDir)
bDir, err := ioutil.TempDir("", "right-") var ocidesc ocispec.Descriptor
if err != nil { if err := mount.WithTempMount(lower, func(lowerRoot string) error {
return emptyDesc, errors.Wrap(err, "failed to create temporary directory") return mount.WithTempMount(upper, func(upperRoot string) error {
} var newReference bool
defer os.Remove(bDir) if config.Reference == "" {
newReference = true
if err := mount.All(lower, aDir); err != nil { config.Reference = uniqueRef()
return emptyDesc, errors.Wrap(err, "failed to mount")
}
defer func() {
if uerr := mount.Unmount(aDir, 0); uerr != nil {
if err == nil {
err = uerr
} }
}
}()
if err := mount.All(upper, bDir); err != nil { cw, err := s.store.Writer(ctx, config.Reference, 0, "")
return emptyDesc, errors.Wrap(err, "failed to mount") if err != nil {
} return errors.Wrap(err, "failed to open writer")
defer func() {
if uerr := mount.Unmount(bDir, 0); uerr != nil {
if err == nil {
err = uerr
} }
} defer func() {
}() if err != nil {
cw.Close()
var newReference bool if newReference {
if config.Reference == "" { if err := s.store.Abort(ctx, config.Reference); err != nil {
newReference = true log.G(ctx).WithField("ref", config.Reference).Warnf("failed to delete diff upload")
config.Reference = uniqueRef() }
} }
}
cw, err := s.store.Writer(ctx, config.Reference, 0, "") }()
if err != nil { if !newReference {
return emptyDesc, errors.Wrap(err, "failed to open writer") if err := cw.Truncate(0); err != nil {
} return err
defer func() {
if err != nil {
cw.Close()
if newReference {
if err := s.store.Abort(ctx, config.Reference); err != nil {
log.G(ctx).WithField("ref", config.Reference).Warnf("failed to delete diff upload")
} }
} }
}
}() if isCompressed {
if !newReference { dgstr := digest.SHA256.Digester()
if err := cw.Truncate(0); err != nil { compressed, err := compression.CompressStream(cw, compression.Gzip)
return emptyDesc, err if err != nil {
} return errors.Wrap(err, "failed to get compressed stream")
}
err = archive.WriteDiff(ctx, io.MultiWriter(compressed, dgstr.Hash()), lowerRoot, upperRoot)
compressed.Close()
if err != nil {
return errors.Wrap(err, "failed to write compressed diff")
}
if config.Labels == nil {
config.Labels = map[string]string{}
}
config.Labels["containerd.io/uncompressed"] = dgstr.Digest().String()
} else {
if err = archive.WriteDiff(ctx, cw, lowerRoot, upperRoot); err != nil {
return errors.Wrap(err, "failed to write diff")
}
}
var commitopts []content.Opt
if config.Labels != nil {
commitopts = append(commitopts, content.WithLabels(config.Labels))
}
dgst := cw.Digest()
if err := cw.Commit(ctx, 0, dgst, commitopts...); err != nil {
return errors.Wrap(err, "failed to commit")
}
info, err := s.store.Info(ctx, dgst)
if err != nil {
return errors.Wrap(err, "failed to get info from content store")
}
ocidesc = ocispec.Descriptor{
MediaType: config.MediaType,
Size: info.Size,
Digest: info.Digest,
}
return nil
})
}); err != nil {
return emptyDesc, err
} }
if isCompressed { return ocidesc, nil
dgstr := digest.SHA256.Digester()
compressed, err := compression.CompressStream(cw, compression.Gzip)
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to get compressed stream")
}
err = archive.WriteDiff(ctx, io.MultiWriter(compressed, dgstr.Hash()), aDir, bDir)
compressed.Close()
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to write compressed diff")
}
if config.Labels == nil {
config.Labels = map[string]string{}
}
config.Labels["containerd.io/uncompressed"] = dgstr.Digest().String()
} else {
if err = archive.WriteDiff(ctx, cw, aDir, bDir); err != nil {
return emptyDesc, errors.Wrap(err, "failed to write diff")
}
}
var commitopts []content.Opt
if config.Labels != nil {
commitopts = append(commitopts, content.WithLabels(config.Labels))
}
dgst := cw.Digest()
if err := cw.Commit(ctx, 0, dgst, commitopts...); err != nil {
return emptyDesc, errors.Wrap(err, "failed to commit")
}
info, err := s.store.Info(ctx, dgst)
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to get info from content store")
}
return ocispec.Descriptor{
MediaType: config.MediaType,
Size: info.Size,
Digest: info.Digest,
}, nil
} }
type readCounter struct { type readCounter struct {

View File

@ -1,5 +1,13 @@
package mount package mount
import (
"io/ioutil"
"os"
"github.com/containerd/containerd/log"
"github.com/pkg/errors"
)
// Mount is the lingua franca of containerd. A mount represents a // Mount is the lingua franca of containerd. A mount represents a
// serialized mount syscall. Components either emit or consume mounts. // serialized mount syscall. Components either emit or consume mounts.
type Mount struct { type Mount struct {
@ -22,3 +30,44 @@ func All(mounts []Mount, target string) error {
} }
return nil return nil
} }
// WithTempMount mounts the provided mounts to a temp dir, and pass the temp dir to f.
// The mounts are valid during the call to the f.
// Finally we will unmount and remove the temp dir regardless of the result of f.
func WithTempMount(mounts []Mount, f func(root string) error) (err error) {
root, uerr := ioutil.TempDir("", "containerd-WithTempMount")
if uerr != nil {
return errors.Wrapf(uerr, "failed to create temp dir for %v", mounts)
}
// We use Remove here instead of RemoveAll.
// The RemoveAll will delete the temp dir and all children it contains.
// When the Unmount fails, if we use RemoveAll, We will incorrectly delete data from mounted dir.
// if we use Remove,even though we won't successfully delete the temp dir,
// but we only leak a temp dir, we don't loss data from mounted dir.
// For details, please refer to #1868 #1785.
defer func() {
if uerr = os.Remove(root); uerr != nil {
log.L.Errorf("Failed to remove the temp dir %s: %v", root, uerr)
}
}()
// We should do defer first, if not we will not do Unmount when only a part of Mounts are failed.
defer func() {
if uerr = UnmountAll(root, 0); uerr != nil {
uerr = errors.Wrapf(uerr, "failed to unmount %s", root)
if err == nil {
err = uerr
} else {
err = errors.Wrap(err, uerr.Error())
}
}
}()
if uerr = All(mounts, root); uerr != nil {
return errors.Wrapf(uerr, "failed to mount %s", root)
}
if uerr = f(root); uerr != nil {
return errors.Wrapf(uerr, "failed to f(%s)", root)
}
return nil
}

View File

@ -6,7 +6,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -271,49 +270,35 @@ func WithUserID(uid uint32) SpecOpts {
if err != nil { if err != nil {
return err return err
} }
root, err := ioutil.TempDir("", "ctd-username")
if err != nil { return mount.WithTempMount(mounts, func(root string) error {
return err ppath, err := fs.RootPath(root, "/etc/passwd")
} if err != nil {
defer os.Remove(root)
for _, m := range mounts {
if err := m.Mount(root); err != nil {
return err return err
} }
} f, err := os.Open(ppath)
defer func() { if err != nil {
if uerr := mount.Unmount(root, 0); uerr != nil { if os.IsNotExist(err) {
if err == nil { s.Process.User.UID, s.Process.User.GID = uid, uid
err = uerr return nil
} }
return err
} }
}() defer f.Close()
ppath, err := fs.RootPath(root, "/etc/passwd") users, err := user.ParsePasswdFilter(f, func(u user.User) bool {
if err != nil { return u.Uid == int(uid)
return err })
} if err != nil {
f, err := os.Open(ppath) return err
if err != nil { }
if os.IsNotExist(err) { if len(users) == 0 {
s.Process.User.UID, s.Process.User.GID = uid, uid s.Process.User.UID, s.Process.User.GID = uid, uid
return nil return nil
} }
return err u := users[0]
} s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
defer f.Close()
users, err := user.ParsePasswdFilter(f, func(u user.User) bool {
return u.Uid == int(uid)
})
if err != nil {
return err
}
if len(users) == 0 {
s.Process.User.UID, s.Process.User.GID = uid, uid
return nil return nil
} })
u := users[0]
s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
return nil
} }
} }
@ -334,43 +319,28 @@ func WithUsername(username string) SpecOpts {
if err != nil { if err != nil {
return err return err
} }
root, err := ioutil.TempDir("", "ctd-username") return mount.WithTempMount(mounts, func(root string) error {
if err != nil { ppath, err := fs.RootPath(root, "/etc/passwd")
return err if err != nil {
}
defer os.Remove(root)
for _, m := range mounts {
if err := m.Mount(root); err != nil {
return err return err
} }
} f, err := os.Open(ppath)
defer func() { if err != nil {
if uerr := mount.Unmount(root, 0); uerr != nil { return err
if err == nil {
err = uerr
}
} }
}() defer f.Close()
ppath, err := fs.RootPath(root, "/etc/passwd") users, err := user.ParsePasswdFilter(f, func(u user.User) bool {
if err != nil { return u.Name == username
return err })
} if err != nil {
f, err := os.Open(ppath) return err
if err != nil { }
return err if len(users) == 0 {
} return errors.Errorf("no users found for %s", username)
defer f.Close() }
users, err := user.ParsePasswdFilter(f, func(u user.User) bool { u := users[0]
return u.Name == username s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
return nil
}) })
if err != nil {
return err
}
if len(users) == 0 {
return errors.Errorf("no users found for %s", username)
}
u := users[0]
s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
return nil
} }
} }