26b1064967
This PR adds a "request ID" to each event generated, the 'docker events' stream now looks like this: ``` 2015-09-10T15:02:50.000000000-07:00 [reqid: c01e3534ddca] de7c5d4ca927253cf4e978ee9c4545161e406e9b5a14617efb52c658b249174a: (from ubuntu) create ``` Note the `[reqID: c01e3534ddca]` part, that's new. Each HTTP request will generate its own unique ID. So, if you do a `docker build` you'll see a series of events all with the same reqID. This allow for log processing tools to determine which events are all related to the same http request. I didn't propigate the context to all possible funcs in the daemon, I decided to just do the ones that needed it in order to get the reqID into the events. I'd like to have people review this direction first, and if we're ok with it then I'll make sure we're consistent about when we pass around the context - IOW, make sure that all funcs at the same level have a context passed in even if they don't call the log funcs - this will ensure we're consistent w/o passing it around for all calls unnecessarily. ping @icecrime @calavera @crosbymichael Signed-off-by: Doug Davis <dug@us.ibm.com>
325 lines
10 KiB
Go
325 lines
10 KiB
Go
package daemon
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/context"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/docker/docker/pkg/chrootarchive"
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
)
|
|
|
|
// ErrExtractPointNotDirectory is used to convey that the operation to extract
|
|
// a tar archive to a directory in a container has failed because the specified
|
|
// path does not refer to a directory.
|
|
var ErrExtractPointNotDirectory = errors.New("extraction point is not a directory")
|
|
|
|
// ContainerCopy performs a deprecated operation of archiving the resource at
|
|
// the specified path in the conatiner identified by the given name.
|
|
func (daemon *Daemon) ContainerCopy(ctx context.Context, name string, res string) (io.ReadCloser, error) {
|
|
container, err := daemon.Get(ctx, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if res[0] == '/' || res[0] == '\\' {
|
|
res = res[1:]
|
|
}
|
|
|
|
return container.copy(ctx, res)
|
|
}
|
|
|
|
// ContainerStatPath stats the filesystem resource at the specified path in the
|
|
// container identified by the given name.
|
|
func (daemon *Daemon) ContainerStatPath(ctx context.Context, name string, path string) (stat *types.ContainerPathStat, err error) {
|
|
container, err := daemon.Get(ctx, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return container.StatPath(ctx, path)
|
|
}
|
|
|
|
// ContainerArchivePath creates an archive of the filesystem resource at the
|
|
// specified path in the container identified by the given name. Returns a
|
|
// tar archive of the resource and whether it was a directory or a single file.
|
|
func (daemon *Daemon) ContainerArchivePath(ctx context.Context, name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
|
|
container, err := daemon.Get(ctx, name)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return container.ArchivePath(ctx, path)
|
|
}
|
|
|
|
// ContainerExtractToDir extracts the given archive to the specified location
|
|
// in the filesystem of the container identified by the given name. The given
|
|
// path must be of a directory in the container. If it is not, the error will
|
|
// be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will
|
|
// be an error if unpacking the given content would cause an existing directory
|
|
// to be replaced with a non-directory and vice versa.
|
|
func (daemon *Daemon) ContainerExtractToDir(ctx context.Context, name, path string, noOverwriteDirNonDir bool, content io.Reader) error {
|
|
container, err := daemon.Get(ctx, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return container.ExtractToDir(ctx, path, noOverwriteDirNonDir, content)
|
|
}
|
|
|
|
// resolvePath resolves the given path in the container to a resource on the
|
|
// host. Returns a resolved path (absolute path to the resource on the host),
|
|
// the absolute path to the resource relative to the container's rootfs, and
|
|
// a error if the path points to outside the container's rootfs.
|
|
func (container *Container) resolvePath(path string) (resolvedPath, absPath string, err error) {
|
|
// Consider the given path as an absolute path in the container.
|
|
absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
|
|
|
|
// Split the absPath into its Directory and Base components. We will
|
|
// resolve the dir in the scope of the container then append the base.
|
|
dirPath, basePath := filepath.Split(absPath)
|
|
|
|
resolvedDirPath, err := container.GetResourcePath(dirPath)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
// resolvedDirPath will have been cleaned (no trailing path separators) so
|
|
// we can manually join it with the base path element.
|
|
resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
|
|
|
|
return resolvedPath, absPath, nil
|
|
}
|
|
|
|
// statPath is the unexported version of StatPath. Locks and mounts should
|
|
// be acquired before calling this method and the given path should be fully
|
|
// resolved to a path on the host corresponding to the given absolute path
|
|
// inside the container.
|
|
func (container *Container) statPath(resolvedPath, absPath string) (stat *types.ContainerPathStat, err error) {
|
|
lstat, err := os.Lstat(resolvedPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var linkTarget string
|
|
if lstat.Mode()&os.ModeSymlink != 0 {
|
|
// Fully evaluate the symlink in the scope of the container rootfs.
|
|
hostPath, err := container.GetResourcePath(absPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
linkTarget, err = filepath.Rel(container.basefs, hostPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Make it an absolute path.
|
|
linkTarget = filepath.Join(string(filepath.Separator), linkTarget)
|
|
}
|
|
|
|
return &types.ContainerPathStat{
|
|
Name: filepath.Base(absPath),
|
|
Size: lstat.Size(),
|
|
Mode: lstat.Mode(),
|
|
Mtime: lstat.ModTime(),
|
|
LinkTarget: linkTarget,
|
|
}, nil
|
|
}
|
|
|
|
// StatPath stats the filesystem resource at the specified path in this
|
|
// container. Returns stat info about the resource.
|
|
func (container *Container) StatPath(ctx context.Context, path string) (stat *types.ContainerPathStat, err error) {
|
|
container.Lock()
|
|
defer container.Unlock()
|
|
|
|
if err = container.Mount(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
defer container.Unmount(ctx)
|
|
|
|
err = container.mountVolumes()
|
|
defer container.unmountVolumes(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resolvedPath, absPath, err := container.resolvePath(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return container.statPath(resolvedPath, absPath)
|
|
}
|
|
|
|
// ArchivePath creates an archive of the filesystem resource at the specified
|
|
// path in this container. Returns a tar archive of the resource and stat info
|
|
// about the resource.
|
|
func (container *Container) ArchivePath(ctx context.Context, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
|
|
container.Lock()
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
// Wait to unlock the container until the archive is fully read
|
|
// (see the ReadCloseWrapper func below) or if there is an error
|
|
// before that occurs.
|
|
container.Unlock()
|
|
}
|
|
}()
|
|
|
|
if err = container.Mount(ctx); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
// unmount any volumes
|
|
container.unmountVolumes(true)
|
|
// unmount the container's rootfs
|
|
container.Unmount(ctx)
|
|
}
|
|
}()
|
|
|
|
if err = container.mountVolumes(); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
resolvedPath, absPath, err := container.resolvePath(path)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
stat, err = container.statPath(resolvedPath, absPath)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// We need to rebase the archive entries if the last element of the
|
|
// resolved path was a symlink that was evaluated and is now different
|
|
// than the requested path. For example, if the given path was "/foo/bar/",
|
|
// but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want
|
|
// to ensure that the archive entries start with "bar" and not "baz". This
|
|
// also catches the case when the root directory of the container is
|
|
// requested: we want the archive entries to start with "/" and not the
|
|
// container ID.
|
|
data, err := archive.TarResourceRebase(resolvedPath, filepath.Base(absPath))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
content = ioutils.NewReadCloserWrapper(data, func() error {
|
|
err := data.Close()
|
|
container.unmountVolumes(true)
|
|
container.Unmount(ctx)
|
|
container.Unlock()
|
|
return err
|
|
})
|
|
|
|
container.logEvent(ctx, "archive-path")
|
|
|
|
return content, stat, nil
|
|
}
|
|
|
|
// ExtractToDir extracts the given tar archive to the specified location in the
|
|
// filesystem of this container. The given path must be of a directory in the
|
|
// container. If it is not, the error will be ErrExtractPointNotDirectory. If
|
|
// noOverwriteDirNonDir is true then it will be an error if unpacking the
|
|
// given content would cause an existing directory to be replaced with a non-
|
|
// directory and vice versa.
|
|
func (container *Container) ExtractToDir(ctx context.Context, path string, noOverwriteDirNonDir bool, content io.Reader) (err error) {
|
|
container.Lock()
|
|
defer container.Unlock()
|
|
|
|
if err = container.Mount(ctx); err != nil {
|
|
return err
|
|
}
|
|
defer container.Unmount(ctx)
|
|
|
|
err = container.mountVolumes()
|
|
defer container.unmountVolumes(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// The destination path needs to be resolved to a host path, with all
|
|
// symbolic links followed in the scope of the container's rootfs. Note
|
|
// that we do not use `container.resolvePath(path)` here because we need
|
|
// to also evaluate the last path element if it is a symlink. This is so
|
|
// that you can extract an archive to a symlink that points to a directory.
|
|
|
|
// Consider the given path as an absolute path in the container.
|
|
absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
|
|
|
|
// This will evaluate the last path element if it is a symlink.
|
|
resolvedPath, err := container.GetResourcePath(absPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stat, err := os.Lstat(resolvedPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !stat.IsDir() {
|
|
return ErrExtractPointNotDirectory
|
|
}
|
|
|
|
// Need to check if the path is in a volume. If it is, it cannot be in a
|
|
// read-only volume. If it is not in a volume, the container cannot be
|
|
// configured with a read-only rootfs.
|
|
|
|
// Use the resolved path relative to the container rootfs as the new
|
|
// absPath. This way we fully follow any symlinks in a volume that may
|
|
// lead back outside the volume.
|
|
//
|
|
// The Windows implementation of filepath.Rel in golang 1.4 does not
|
|
// support volume style file path semantics. On Windows when using the
|
|
// filter driver, we are guaranteed that the path will always be
|
|
// a volume file path.
|
|
var baseRel string
|
|
if strings.HasPrefix(resolvedPath, `\\?\Volume{`) {
|
|
if strings.HasPrefix(resolvedPath, container.basefs) {
|
|
baseRel = resolvedPath[len(container.basefs):]
|
|
if baseRel[:1] == `\` {
|
|
baseRel = baseRel[1:]
|
|
}
|
|
}
|
|
} else {
|
|
baseRel, err = filepath.Rel(container.basefs, resolvedPath)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Make it an absolute path.
|
|
absPath = filepath.Join(string(filepath.Separator), baseRel)
|
|
|
|
toVolume, err := checkIfPathIsInAVolume(container, absPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !toVolume && container.hostConfig.ReadonlyRootfs {
|
|
return ErrRootFSReadOnly
|
|
}
|
|
|
|
options := &archive.TarOptions{
|
|
ChownOpts: &archive.TarChownOptions{
|
|
UID: 0, GID: 0, // TODO: use config.User? Remap to userns root?
|
|
},
|
|
NoOverwriteDirNonDir: noOverwriteDirNonDir,
|
|
}
|
|
|
|
if err := chrootarchive.Untar(content, resolvedPath, options); err != nil {
|
|
return err
|
|
}
|
|
|
|
container.logEvent(ctx, "extract-to-dir")
|
|
|
|
return nil
|
|
}
|