2018-02-05 16:05:59 -05:00
|
|
|
package containerfs // import "github.com/docker/docker/pkg/containerfs"
|
2017-08-03 20:22:00 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/tar"
|
pkg/system: move EnsureRemoveAll() to pkg/containerfs
pkg/system historically has been a bit of a kitchen-sink of things that were
somewhat "system" related, but didn't have a good place for. EnsureRemoveAll()
is one of those utilities. EnsureRemoveAll() is used to both unmount and remove
a path, for which it depends on both github.com/moby/sys/mount, which in turn
depends on github.com/moby/sys/mountinfo.
pkg/system is imported in the CLI, but neither EnsureRemoveAll(), nor any of its
moby/sys dependencies are used on the client side, so let's move this function
somewhere else, to remove those dependencies from the CLI.
I looked for plausible locations that were related; it's used in:
- daemon
- daemon/graphdriver/XXX/
- plugin
I considered moving it into a (e.g.) "utils" package within graphdriver (but not
a huge fan of "utils" packages), and given that it felt (mostly) related to
cleaning up container filesystems, I decided to move it there.
Some things to follow-up on after this:
- Verify if this function is still needed (it feels a bit like a big hammer in
a "YOLO, let's try some things just in case it fails")
- Perhaps it should be integrated in `containerfs.Remove()` (so that it's used
automatically)
- Look if there's other implementations (and if they should be consolidated),
although (e.g.) the one in containerd is a copy of ours:
https://github.com/containerd/containerd/blob/v1.5.9/pkg/cri/server/helpers_linux.go#L200
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-02 16:43:07 -05:00
|
|
|
"errors"
|
2017-08-03 20:22:00 -04:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2018-04-02 13:48:34 -04:00
|
|
|
"time"
|
2017-08-03 20:22:00 -04:00
|
|
|
|
|
|
|
"github.com/docker/docker/pkg/archive"
|
|
|
|
"github.com/docker/docker/pkg/idtools"
|
|
|
|
"github.com/docker/docker/pkg/system"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TarFunc provides a function definition for a custom Tar function
|
|
|
|
type TarFunc func(string, *archive.TarOptions) (io.ReadCloser, error)
|
|
|
|
|
|
|
|
// UntarFunc provides a function definition for a custom Untar function
|
|
|
|
type UntarFunc func(io.Reader, string, *archive.TarOptions) error
|
|
|
|
|
|
|
|
// Archiver provides a similar implementation of the archive.Archiver package with the rootfs abstraction
|
|
|
|
type Archiver struct {
|
2017-11-16 01:20:33 -05:00
|
|
|
SrcDriver Driver
|
|
|
|
DstDriver Driver
|
|
|
|
Tar TarFunc
|
|
|
|
Untar UntarFunc
|
|
|
|
IDMapping *idtools.IdentityMapping
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other.
|
|
|
|
// If either Tar or Untar fails, TarUntar aborts and returns the error.
|
|
|
|
func (archiver *Archiver) TarUntar(src, dst string) error {
|
|
|
|
logrus.Debugf("TarUntar(%s %s)", src, dst)
|
|
|
|
tarArchive, err := archiver.Tar(src, &archive.TarOptions{Compression: archive.Uncompressed})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tarArchive.Close()
|
|
|
|
options := &archive.TarOptions{
|
2017-11-16 01:20:33 -05:00
|
|
|
UIDMaps: archiver.IDMapping.UIDs(),
|
|
|
|
GIDMaps: archiver.IDMapping.GIDs(),
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
return archiver.Untar(tarArchive, dst, options)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UntarPath untar a file from path to a destination, src is the source tar file path.
|
|
|
|
func (archiver *Archiver) UntarPath(src, dst string) error {
|
|
|
|
tarArchive, err := archiver.SrcDriver.Open(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tarArchive.Close()
|
|
|
|
options := &archive.TarOptions{
|
2017-11-16 01:20:33 -05:00
|
|
|
UIDMaps: archiver.IDMapping.UIDs(),
|
|
|
|
GIDMaps: archiver.IDMapping.GIDs(),
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
return archiver.Untar(tarArchive, dst, options)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CopyWithTar creates a tar archive of filesystem path `src`, and
|
|
|
|
// unpacks it at filesystem path `dst`.
|
|
|
|
// The archive is streamed directly with fixed buffering and no
|
|
|
|
// intermediary disk IO.
|
|
|
|
func (archiver *Archiver) CopyWithTar(src, dst string) error {
|
|
|
|
srcSt, err := archiver.SrcDriver.Stat(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !srcSt.IsDir() {
|
|
|
|
return archiver.CopyFileWithTar(src, dst)
|
|
|
|
}
|
|
|
|
|
|
|
|
// if this archiver is set up with ID mapping we need to create
|
|
|
|
// the new destination directory with the remapped root UID/GID pair
|
|
|
|
// as owner
|
2017-11-16 01:20:33 -05:00
|
|
|
|
|
|
|
identity := idtools.Identity{UID: archiver.IDMapping.RootPair().UID, GID: archiver.IDMapping.RootPair().GID}
|
|
|
|
|
2017-08-03 20:22:00 -04:00
|
|
|
// Create dst, copy src's content into it
|
2017-11-16 01:20:33 -05:00
|
|
|
if err := idtools.MkdirAllAndChownNew(dst, 0755, identity); err != nil {
|
2017-08-03 20:22:00 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
logrus.Debugf("Calling TarUntar(%s, %s)", src, dst)
|
|
|
|
return archiver.TarUntar(src, dst)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CopyFileWithTar emulates the behavior of the 'cp' command-line
|
|
|
|
// for a single file. It copies a regular file from path `src` to
|
|
|
|
// path `dst`, and preserves all its metadata.
|
2019-08-27 13:18:39 -04:00
|
|
|
func (archiver *Archiver) CopyFileWithTar(src, dst string) (retErr error) {
|
2017-08-03 20:22:00 -04:00
|
|
|
logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst)
|
|
|
|
srcDriver := archiver.SrcDriver
|
|
|
|
dstDriver := archiver.DstDriver
|
|
|
|
|
2019-08-27 13:18:39 -04:00
|
|
|
srcSt, retErr := srcDriver.Stat(src)
|
|
|
|
if retErr != nil {
|
|
|
|
return retErr
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if srcSt.IsDir() {
|
pkg/system: move EnsureRemoveAll() to pkg/containerfs
pkg/system historically has been a bit of a kitchen-sink of things that were
somewhat "system" related, but didn't have a good place for. EnsureRemoveAll()
is one of those utilities. EnsureRemoveAll() is used to both unmount and remove
a path, for which it depends on both github.com/moby/sys/mount, which in turn
depends on github.com/moby/sys/mountinfo.
pkg/system is imported in the CLI, but neither EnsureRemoveAll(), nor any of its
moby/sys dependencies are used on the client side, so let's move this function
somewhere else, to remove those dependencies from the CLI.
I looked for plausible locations that were related; it's used in:
- daemon
- daemon/graphdriver/XXX/
- plugin
I considered moving it into a (e.g.) "utils" package within graphdriver (but not
a huge fan of "utils" packages), and given that it felt (mostly) related to
cleaning up container filesystems, I decided to move it there.
Some things to follow-up on after this:
- Verify if this function is still needed (it feels a bit like a big hammer in
a "YOLO, let's try some things just in case it fails")
- Perhaps it should be integrated in `containerfs.Remove()` (so that it's used
automatically)
- Look if there's other implementations (and if they should be consolidated),
although (e.g.) the one in containerd is a copy of ours:
https://github.com/containerd/containerd/blob/v1.5.9/pkg/cri/server/helpers_linux.go#L200
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-02 16:43:07 -05:00
|
|
|
return errors.New("cannot copy a directory")
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clean up the trailing slash. This must be done in an operating
|
|
|
|
// system specific manner.
|
|
|
|
if dst[len(dst)-1] == dstDriver.Separator() {
|
|
|
|
dst = dstDriver.Join(dst, srcDriver.Base(src))
|
|
|
|
}
|
|
|
|
|
|
|
|
// The original call was system.MkdirAll, which is just
|
|
|
|
// os.MkdirAll on not-Windows and changed for Windows.
|
|
|
|
if dstDriver.OS() == "windows" {
|
|
|
|
// Now we are WCOW
|
2019-08-08 05:51:00 -04:00
|
|
|
if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil {
|
2017-08-03 20:22:00 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// We can just use the driver.MkdirAll function
|
|
|
|
if err := dstDriver.MkdirAll(dstDriver.Dir(dst), 0700); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
r, w := io.Pipe()
|
2017-09-21 20:56:45 -04:00
|
|
|
errC := make(chan error, 1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer close(errC)
|
|
|
|
errC <- func() error {
|
|
|
|
defer w.Close()
|
|
|
|
|
|
|
|
srcF, err := srcDriver.Open(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer srcF.Close()
|
|
|
|
|
2022-01-24 15:33:54 -05:00
|
|
|
hdr, err := archive.FileInfoHeaderNoLookups(srcSt, "")
|
2017-09-21 20:56:45 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-04-02 13:48:34 -04:00
|
|
|
hdr.Format = tar.FormatPAX
|
|
|
|
hdr.ModTime = hdr.ModTime.Truncate(time.Second)
|
|
|
|
hdr.AccessTime = time.Time{}
|
|
|
|
hdr.ChangeTime = time.Time{}
|
2017-09-21 20:56:45 -04:00
|
|
|
hdr.Name = dstDriver.Base(dst)
|
|
|
|
if dstDriver.OS() == "windows" {
|
|
|
|
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
|
|
|
|
} else {
|
|
|
|
hdr.Mode = int64(os.FileMode(hdr.Mode))
|
|
|
|
}
|
|
|
|
|
2017-11-16 01:20:33 -05:00
|
|
|
if err := remapIDs(archiver.IDMapping, hdr); err != nil {
|
2017-09-21 20:56:45 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tw := tar.NewWriter(w)
|
|
|
|
defer tw.Close()
|
|
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := io.Copy(tw, srcF); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}()
|
|
|
|
}()
|
2017-08-03 20:22:00 -04:00
|
|
|
defer func() {
|
2019-08-27 13:18:39 -04:00
|
|
|
if err := <-errC; retErr == nil && err != nil {
|
|
|
|
retErr = err
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2019-08-27 13:18:39 -04:00
|
|
|
retErr = archiver.Untar(r, dstDriver.Dir(dst), nil)
|
|
|
|
if retErr != nil {
|
|
|
|
r.CloseWithError(retErr)
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
2019-08-27 13:18:39 -04:00
|
|
|
return retErr
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
|
2017-11-16 01:20:33 -05:00
|
|
|
// IdentityMapping returns the IdentityMapping of the archiver.
|
|
|
|
func (archiver *Archiver) IdentityMapping() *idtools.IdentityMapping {
|
|
|
|
return archiver.IDMapping
|
2017-08-03 20:22:00 -04:00
|
|
|
}
|
|
|
|
|
2017-11-16 01:20:33 -05:00
|
|
|
func remapIDs(idMapping *idtools.IdentityMapping, hdr *tar.Header) error {
|
|
|
|
ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid})
|
2017-08-03 20:22:00 -04:00
|
|
|
hdr.Uid, hdr.Gid = ids.UID, ids.GID
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
|
|
|
// on the platform the archival is done.
|
|
|
|
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
2019-11-27 09:39:57 -05:00
|
|
|
// perm &= 0755 // this 0-ed out tar flags (like link, regular file, directory marker etc.)
|
2017-08-03 20:22:00 -04:00
|
|
|
permPart := perm & os.ModePerm
|
|
|
|
noPermPart := perm &^ os.ModePerm
|
|
|
|
// Add the x bit: make everything +x from windows
|
|
|
|
permPart |= 0111
|
|
|
|
permPart &= 0755
|
|
|
|
|
|
|
|
return noPermPart | permPart
|
|
|
|
}
|