github.com/hamo/docker@v1.11.1/api/client/cp.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"golang.org/x/net/context"
    11  
    12  	Cli "github.com/docker/docker/cli"
    13  	"github.com/docker/docker/pkg/archive"
    14  	flag "github.com/docker/docker/pkg/mflag"
    15  	"github.com/docker/docker/pkg/system"
    16  	"github.com/docker/engine-api/types"
    17  )
    18  
    19  type copyDirection int
    20  
    21  const (
    22  	fromContainer copyDirection = (1 << iota)
    23  	toContainer
    24  	acrossContainers = fromContainer | toContainer
    25  )
    26  
    27  type cpConfig struct {
    28  	followLink bool
    29  }
    30  
    31  // CmdCp copies files/folders to or from a path in a container.
    32  //
    33  // When copying from a container, if DEST_PATH is '-' the data is written as a
    34  // tar archive file to STDOUT.
    35  //
    36  // When copying to a container, if SRC_PATH is '-' the data is read as a tar
    37  // archive file from STDIN, and the destination CONTAINER:DEST_PATH, must specify
    38  // a directory.
    39  //
    40  // Usage:
    41  // 	docker cp CONTAINER:SRC_PATH DEST_PATH|-
    42  // 	docker cp SRC_PATH|- CONTAINER:DEST_PATH
    43  func (cli *DockerCli) CmdCp(args ...string) error {
    44  	cmd := Cli.Subcmd(
    45  		"cp",
    46  		[]string{"CONTAINER:SRC_PATH DEST_PATH|-", "SRC_PATH|- CONTAINER:DEST_PATH"},
    47  		strings.Join([]string{
    48  			Cli.DockerCommands["cp"].Description,
    49  			"\nUse '-' as the source to read a tar archive from stdin\n",
    50  			"and extract it to a directory destination in a container.\n",
    51  			"Use '-' as the destination to stream a tar archive of a\n",
    52  			"container source to stdout.",
    53  		}, ""),
    54  		true,
    55  	)
    56  
    57  	followLink := cmd.Bool([]string{"L", "-follow-link"}, false, "Always follow symbol link in SRC_PATH")
    58  
    59  	cmd.Require(flag.Exact, 2)
    60  	cmd.ParseFlags(args, true)
    61  
    62  	if cmd.Arg(0) == "" {
    63  		return fmt.Errorf("source can not be empty")
    64  	}
    65  	if cmd.Arg(1) == "" {
    66  		return fmt.Errorf("destination can not be empty")
    67  	}
    68  
    69  	srcContainer, srcPath := splitCpArg(cmd.Arg(0))
    70  	dstContainer, dstPath := splitCpArg(cmd.Arg(1))
    71  
    72  	var direction copyDirection
    73  	if srcContainer != "" {
    74  		direction |= fromContainer
    75  	}
    76  	if dstContainer != "" {
    77  		direction |= toContainer
    78  	}
    79  
    80  	cpParam := &cpConfig{
    81  		followLink: *followLink,
    82  	}
    83  
    84  	switch direction {
    85  	case fromContainer:
    86  		return cli.copyFromContainer(srcContainer, srcPath, dstPath, cpParam)
    87  	case toContainer:
    88  		return cli.copyToContainer(srcPath, dstContainer, dstPath, cpParam)
    89  	case acrossContainers:
    90  		// Copying between containers isn't supported.
    91  		return fmt.Errorf("copying between containers is not supported")
    92  	default:
    93  		// User didn't specify any container.
    94  		return fmt.Errorf("must specify at least one container source")
    95  	}
    96  }
    97  
    98  // We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
    99  // in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
   100  // requiring a LOCALPATH with a `:` to be made explicit with a relative or
   101  // absolute path:
   102  // 	`/path/to/file:name.txt` or `./file:name.txt`
   103  //
   104  // This is apparently how `scp` handles this as well:
   105  // 	http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
   106  //
   107  // We can't simply check for a filepath separator because container names may
   108  // have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
   109  // so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
   110  // client, a `:` could be part of an absolute Windows path, in which case it
   111  // is immediately proceeded by a backslash.
   112  func splitCpArg(arg string) (container, path string) {
   113  	if system.IsAbs(arg) {
   114  		// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
   115  		return "", arg
   116  	}
   117  
   118  	parts := strings.SplitN(arg, ":", 2)
   119  
   120  	if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
   121  		// Either there's no `:` in the arg
   122  		// OR it's an explicit local relative path like `./file:name.txt`.
   123  		return "", arg
   124  	}
   125  
   126  	return parts[0], parts[1]
   127  }
   128  
   129  func (cli *DockerCli) statContainerPath(containerName, path string) (types.ContainerPathStat, error) {
   130  	return cli.client.ContainerStatPath(context.Background(), containerName, path)
   131  }
   132  
   133  func resolveLocalPath(localPath string) (absPath string, err error) {
   134  	if absPath, err = filepath.Abs(localPath); err != nil {
   135  		return
   136  	}
   137  
   138  	return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
   139  }
   140  
   141  func (cli *DockerCli) copyFromContainer(srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) {
   142  	if dstPath != "-" {
   143  		// Get an absolute destination path.
   144  		dstPath, err = resolveLocalPath(dstPath)
   145  		if err != nil {
   146  			return err
   147  		}
   148  	}
   149  
   150  	// if client requests to follow symbol link, then must decide target file to be copied
   151  	var rebaseName string
   152  	if cpParam.followLink {
   153  		srcStat, err := cli.statContainerPath(srcContainer, srcPath)
   154  
   155  		// If the destination is a symbolic link, we should follow it.
   156  		if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
   157  			linkTarget := srcStat.LinkTarget
   158  			if !system.IsAbs(linkTarget) {
   159  				// Join with the parent directory.
   160  				srcParent, _ := archive.SplitPathDirEntry(srcPath)
   161  				linkTarget = filepath.Join(srcParent, linkTarget)
   162  			}
   163  
   164  			linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget)
   165  			srcPath = linkTarget
   166  		}
   167  
   168  	}
   169  
   170  	content, stat, err := cli.client.CopyFromContainer(context.Background(), srcContainer, srcPath)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	defer content.Close()
   175  
   176  	if dstPath == "-" {
   177  		// Send the response to STDOUT.
   178  		_, err = io.Copy(os.Stdout, content)
   179  
   180  		return err
   181  	}
   182  
   183  	// Prepare source copy info.
   184  	srcInfo := archive.CopyInfo{
   185  		Path:       srcPath,
   186  		Exists:     true,
   187  		IsDir:      stat.Mode.IsDir(),
   188  		RebaseName: rebaseName,
   189  	}
   190  
   191  	preArchive := content
   192  	if len(srcInfo.RebaseName) != 0 {
   193  		_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
   194  		preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
   195  	}
   196  	// See comments in the implementation of `archive.CopyTo` for exactly what
   197  	// goes into deciding how and whether the source archive needs to be
   198  	// altered for the correct copy behavior.
   199  	return archive.CopyTo(preArchive, srcInfo, dstPath)
   200  }
   201  
   202  func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string, cpParam *cpConfig) (err error) {
   203  	if srcPath != "-" {
   204  		// Get an absolute source path.
   205  		srcPath, err = resolveLocalPath(srcPath)
   206  		if err != nil {
   207  			return err
   208  		}
   209  	}
   210  
   211  	// In order to get the copy behavior right, we need to know information
   212  	// about both the source and destination. The API is a simple tar
   213  	// archive/extract API but we can use the stat info header about the
   214  	// destination to be more informed about exactly what the destination is.
   215  
   216  	// Prepare destination copy info by stat-ing the container path.
   217  	dstInfo := archive.CopyInfo{Path: dstPath}
   218  	dstStat, err := cli.statContainerPath(dstContainer, dstPath)
   219  
   220  	// If the destination is a symbolic link, we should evaluate it.
   221  	if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
   222  		linkTarget := dstStat.LinkTarget
   223  		if !system.IsAbs(linkTarget) {
   224  			// Join with the parent directory.
   225  			dstParent, _ := archive.SplitPathDirEntry(dstPath)
   226  			linkTarget = filepath.Join(dstParent, linkTarget)
   227  		}
   228  
   229  		dstInfo.Path = linkTarget
   230  		dstStat, err = cli.statContainerPath(dstContainer, linkTarget)
   231  	}
   232  
   233  	// Ignore any error and assume that the parent directory of the destination
   234  	// path exists, in which case the copy may still succeed. If there is any
   235  	// type of conflict (e.g., non-directory overwriting an existing directory
   236  	// or vice versa) the extraction will fail. If the destination simply did
   237  	// not exist, but the parent directory does, the extraction will still
   238  	// succeed.
   239  	if err == nil {
   240  		dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
   241  	}
   242  
   243  	var (
   244  		content         io.Reader
   245  		resolvedDstPath string
   246  	)
   247  
   248  	if srcPath == "-" {
   249  		// Use STDIN.
   250  		content = os.Stdin
   251  		resolvedDstPath = dstInfo.Path
   252  		if !dstInfo.IsDir {
   253  			return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
   254  		}
   255  	} else {
   256  		// Prepare source copy info.
   257  		srcInfo, err := archive.CopyInfoSourcePath(srcPath, cpParam.followLink)
   258  		if err != nil {
   259  			return err
   260  		}
   261  
   262  		srcArchive, err := archive.TarResource(srcInfo)
   263  		if err != nil {
   264  			return err
   265  		}
   266  		defer srcArchive.Close()
   267  
   268  		// With the stat info about the local source as well as the
   269  		// destination, we have enough information to know whether we need to
   270  		// alter the archive that we upload so that when the server extracts
   271  		// it to the specified directory in the container we get the desired
   272  		// copy behavior.
   273  
   274  		// See comments in the implementation of `archive.PrepareArchiveCopy`
   275  		// for exactly what goes into deciding how and whether the source
   276  		// archive needs to be altered for the correct copy behavior when it is
   277  		// extracted. This function also infers from the source and destination
   278  		// info which directory to extract to, which may be the parent of the
   279  		// destination that the user specified.
   280  		dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
   281  		if err != nil {
   282  			return err
   283  		}
   284  		defer preparedArchive.Close()
   285  
   286  		resolvedDstPath = dstDir
   287  		content = preparedArchive
   288  	}
   289  
   290  	options := types.CopyToContainerOptions{
   291  		ContainerID:               dstContainer,
   292  		Path:                      resolvedDstPath,
   293  		Content:                   content,
   294  		AllowOverwriteDirWithFile: false,
   295  	}
   296  
   297  	return cli.client.CopyToContainer(context.Background(), options)
   298  }