1package client
2
3import (
4	"encoding/base64"
5	"encoding/json"
6	"fmt"
7	"io"
8	"net/http"
9	"net/url"
10	"path/filepath"
11	"strings"
12
13	"golang.org/x/net/context"
14
15	"github.com/docker/docker/api/types"
16)
17
18// ContainerStatPath returns Stat information about a path inside the container filesystem.
19func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) {
20	query := url.Values{}
21	query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
22
23	urlStr := "/containers/" + containerID + "/archive"
24	response, err := cli.head(ctx, urlStr, query, nil)
25	if err != nil {
26		return types.ContainerPathStat{}, err
27	}
28	defer ensureReaderClosed(response)
29	return getContainerPathStatFromHeader(response.header)
30}
31
32// CopyToContainer copies content into the container filesystem.
33// Note that `content` must be a Reader for a TAR
34func (cli *Client) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error {
35	query := url.Values{}
36	query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
37	// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
38	if !options.AllowOverwriteDirWithFile {
39		query.Set("noOverwriteDirNonDir", "true")
40	}
41
42	if options.CopyUIDGID {
43		query.Set("copyUIDGID", "true")
44	}
45
46	apiPath := "/containers/" + container + "/archive"
47
48	response, err := cli.putRaw(ctx, apiPath, query, content, nil)
49	if err != nil {
50		return err
51	}
52	defer ensureReaderClosed(response)
53
54	if response.statusCode != http.StatusOK {
55		return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
56	}
57
58	return nil
59}
60
61// CopyFromContainer gets the content from the container and returns it as a Reader
62// to manipulate it in the host. It's up to the caller to close the reader.
63func (cli *Client) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
64	query := make(url.Values, 1)
65	query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
66
67	apiPath := "/containers/" + container + "/archive"
68	response, err := cli.get(ctx, apiPath, query, nil)
69	if err != nil {
70		return nil, types.ContainerPathStat{}, err
71	}
72
73	if response.statusCode != http.StatusOK {
74		return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
75	}
76
77	// In order to get the copy behavior right, we need to know information
78	// about both the source and the destination. The response headers include
79	// stat info about the source that we can use in deciding exactly how to
80	// copy it locally. Along with the stat info about the local destination,
81	// we have everything we need to handle the multiple possibilities there
82	// can be when copying a file/dir from one location to another file/dir.
83	stat, err := getContainerPathStatFromHeader(response.header)
84	if err != nil {
85		return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
86	}
87	return response.body, stat, err
88}
89
90func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
91	var stat types.ContainerPathStat
92
93	encodedStat := header.Get("X-Docker-Container-Path-Stat")
94	statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
95
96	err := json.NewDecoder(statDecoder).Decode(&stat)
97	if err != nil {
98		err = fmt.Errorf("unable to decode container path stat header: %s", err)
99	}
100
101	return stat, err
102}
103